From d899c4d5a121d762fee926ba471dd767ec5b914b Mon Sep 17 00:00:00 2001 From: <> Date: Wed, 9 Oct 2024 14:13:21 +0000 Subject: [PATCH] Deployed ab8d9ff with MkDocs version: 1.5.3 --- FAQ/index.html | 4 +- fr/index.html | 2 +- fr/sitemap.xml | 280 ++++++++++++++-------------- fr/sitemap.xml.gz | Bin 1157 -> 1157 bytes index.html | 2 +- it/FAQ/index.html | 4 +- it/index.html | 2 +- it/search/search_index.json | 2 +- it/sitemap.xml | 278 ++++++++++++++-------------- it/sitemap.xml.gz | Bin 1140 -> 1141 bytes pt/FAQ/index.html | 4 +- pt/index.html | 2 +- pt/search/search_index.json | 2 +- pt/sitemap.xml | 278 ++++++++++++++-------------- pt/sitemap.xml.gz | Bin 1140 -> 1141 bytes search/search_index.json | 2 +- sitemap.xml | 352 ++++++++++++++++++------------------ sitemap.xml.gz | Bin 1479 -> 1479 bytes uk/FAQ/index.html | 4 +- uk/index.html | 2 +- uk/search/search_index.json | 2 +- uk/sitemap.xml | 278 ++++++++++++++-------------- uk/sitemap.xml.gz | Bin 1140 -> 1141 bytes 23 files changed, 750 insertions(+), 750 deletions(-) diff --git a/FAQ/index.html b/FAQ/index.html index c9c557d05..5e584e1e4 100644 --- a/FAQ/index.html +++ b/FAQ/index.html @@ -1177,7 +1177,7 @@

How to manage ownership of my t

Open the team site to which you want add a second owner.

  • -

    Click ‘Manage Users’ under the user menu by clicking on the profile icon in the top-right of Grist.

    +

    Click ‘Manage Team’ under the user menu by clicking on the profile icon in the top-right of Grist.

  • Add the new email address as Owner, and click Confirm.

    @@ -1195,7 +1195,7 @@

    How to manage ownership of my t

    Go to ‘Billing Account’ (also under the user menu) and add the new Owner as a Billing Manager.

  • -

    The new Owner should log in, open the team site, and visit ‘Manage Users’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

    +

    The new Owner should log in, open the team site, and visit ‘Manage Team’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

  • It is not possible to add a second owner to, or transfer ownership of, a personal account.

    diff --git a/fr/index.html b/fr/index.html index 930ca1701..ff4388af2 100644 --- a/fr/index.html +++ b/fr/index.html @@ -1087,5 +1087,5 @@

    Contactez-nous https://support.getgrist.com/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/FAQ/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/access-rules/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/afterschool-program/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/ai-assistant/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/authorship/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/automatic-backups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/browser-support/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-refs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-transform/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-types/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/conditional-formatting/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/copying-docs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/creating-doc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/custom-layouts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/data-security/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/dates/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/document-history/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/embedding/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/enter-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/exports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-cheat-sheet/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-timer/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formulas/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/glossary/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/imports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/integrators/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/investment-research/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/keyboard-shortcuts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/lightweight-crm/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/limits/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/linking-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/on-demand-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/page-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/python/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/raw-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/record-cards/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/references-lookups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/register-as-consultant/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/rest-api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/search-sort-filter/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/self-managed/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/summary-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/team-sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/teams/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-full/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-limited/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/timestamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/webhooks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-calendar/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-chart/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-custom/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-form/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-table/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/workspaces/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-book-club/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-credit-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-07-email-compose/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-08-invoices/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-09-payroll/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-10-print-labels/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-11-treasure-hunt/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-12-map/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-01-tasks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-03-leads/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-04-link-keys/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-05-reference-columns/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-06-timesheets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-07-auto-stamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-01-acl-memo/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-07-proposals-contracts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/aws-marketplace/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/cloud-storage/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/example-docker-nginx/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/forwarded-headers/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/grist-connect/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/oidc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/saml/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/grist_plugin_api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-08/ - 2024-10-03 + 2024-10-09 daily \ No newline at end of file diff --git a/fr/sitemap.xml.gz b/fr/sitemap.xml.gz index 8d655cfabc2054881e7d24de057aadda0e43d7f5..6131a5796383cc6039f482ffa73b7d15f8535b11 100644 GIT binary patch delta 977 zcmZqWY~|#T@8;mB>Ss%z$Z@2eS-wd6cgYM%gD)k1U%qd2e!cGeY=7nV8ngMQ{rCO) z_cs5+lnkDyJefsPmMYETK5uM%r<>fa^0o{ zPQ^3bR&M?@<D)U#hXqr-ubyOAsPMxD6tY*xiy7$6Y5yR3s0zXZa zx4V^WlRxG2)mmbs?1IhrxcCxxT**t_Cfo15l*c69eW~6PPSqYpt4|VE8WoeG`+9Fo z{!nZicUqu1U`Na5Cus^3JLhcYRe!G|_R*)aX(5lE+d=IuomF)w&dR45#F+Z|eb{9a zF2Y){#i{<|z00WkG`MK1J8PKRvPiZm>x=g2VPiU$W~I zX*>6MT=SoPeln-x#L|Ca)t2=IUQajAyW14EW_h|5Z;VIB(de#Ry^}Jne3MH~UEx%J zvL?1gS#SIG6l1P^iFL{`TpTMq3}ZiaxP6G7e%n`ZSwahYy+vUg%j0E}#S$j3*t|&i zlKzQ)hk5ob(XRH#e5@ACKhSnP^!RR;Dd{wdq=soMyG*) zXTppl>&pKy-F3M8U0c4CJ-Sc-nfEou+fB-ErW|;;@ZOmQ?%1ge_YWUW(#SsSYp20j zAF)kQ=eFt;0eh#r5`FEx6-J@dCRL=du=YbZ} z^E1yp#I+VbE{d~QTUhvN!MkUNV#LFnXB7CYT|fbHcMS{H(lbN<-s52v9+Rp`LW;DruXu%RO}CY{MW7byEI!3NTDkq<@;e3p%Dv>SO!(IpRPuA;N}2zRr}w_}mWyFv000N)*RlWr delta 977 zcmZqWY~|#T@8;lmIrU%KM2;i%)A)Dx)>@t6k*Km#|MmOE!qV%{&n}->KIJU?>Ho`q z{d?OUU}Vx()MjFNDoF5r>+_kCNhJo$Zm3VGnst%s*_rbOA}c$BC6i7y%RXgt>pv=G zv2dr);wzDrUWMnTJovUK!0YgJpGC!DJ>f!aHGL<&)AsbKn71w5+-lmxmnE@LWLo`( zh#MbIm0WODHk;SE;f};oCBK=IRV?-#Tj|l8+|H^vdFCYD@SQzL>$B(8O#|vVWWdwM_0k6BwN*+JxTuG zM0|8hmVElE_pB$$cr+lw&Wf%!n;UZ|y#7)D8ZW;ooRcM6CdIv|>Y8EY*YRi8 z#As!!+x)4^e#!RS;JXl+&)RMfleOLGcHi-(L2R=&FAfs_)FO0@LAH`dR>bC{?s2y_ zoVY)hlA> zX?HPML@lhZc>k0yrH=93HPMZ_K5FG`evjvsj>mVSynpXaE5?XvM)Ha(Rik93ckO;71_YWK8Cebpjd zbWQJ&;Pl(snn zFU6m@E1Z`2M(sk>)#=HgZeoyV+)vSA5{IlxohTMaKWnKy21M<@jw661Nc>gfjaLU$)YVuPW z>~(GnOnWQj)giwykB6=PV(**uC)?DxO!#bmu4w+g@!h)Tm)P%aii;DHI=Gv)Lajiw zu{f=G3HQ{Cg_inruI;cW4JiMds6Y3sndK|J8{DcKdwKH@P28zIZ|B_wd~DO?R9{Xm zms~kLN+Gyqv)gUShaHl8gE#zrxL0sjxbv?CZC?U;Urv^~)GT)CZW!-NKi>Mx#m!sF zo@RM`J+dX#8K|aeecKmD-j|D|E~%R?Vb@&}wP%0uAm}_KmLpB{r){`&;IGJYj@Uf%T02hbn%Dq s^m>~;JpX0xUb+1tIM3q0wJLv>N4>M=u0JP2`u;Oq7TContact usHow to manage ownership of my t

    Open the team site to which you want add a second owner.

  • -

    Click ‘Manage Users’ under the user menu by clicking on the profile icon in the top-right of Grist.

    +

    Click ‘Manage Team’ under the user menu by clicking on the profile icon in the top-right of Grist.

  • Add the new email address as Owner, and click Confirm.

    @@ -1065,7 +1065,7 @@

    How to manage ownership of my t

    Go to ‘Billing Account’ (also under the user menu) and add the new Owner as a Billing Manager.

  • -

    The new Owner should log in, open the team site, and visit ‘Manage Users’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

    +

    The new Owner should log in, open the team site, and visit ‘Manage Team’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

  • It is not possible to add a second owner to, or transfer ownership of, a personal account.

    diff --git a/it/index.html b/it/index.html index 1e0271957..3c8eeea0e 100644 --- a/it/index.html +++ b/it/index.html @@ -1098,5 +1098,5 @@

    Contact us Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Users\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Users\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"lightweight-crm/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"investment-research/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"afterschool-program/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"creating-doc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"copying-docs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"imports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"exports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"automatic-backups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"document-history/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"workspaces/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"enter-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"page-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"raw-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"search-sort-filter/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"widget-table/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"widget-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-form/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-chart/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-calendar/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-custom/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord

    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"linking-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"custom-layouts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"record-cards/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"summary-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"on-demand-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"col-types/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"col-refs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"conditional-formatting/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"timestamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"authorship/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"col-transform/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"formulas/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"references-lookups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"dates/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"formula-timer/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"python/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"formula-cheat-sheet/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"ai-assistant/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"teams/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"team-sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"access-rules/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"rest-api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.
    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"integrators/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"embedding/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"webhooks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . description: How to configure webhooks for some external integrations # Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#description-how-to-configure-webhooks-for-some-external-integrations","text":"","title":"description: How to configure webhooks for some external integrations"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"code/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/modules/grist_plugin_api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"self-managed/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"install/saml/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"install/oidc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/forwarded-headers/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/cloud-storage/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/grist-connect/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/aws-marketplace/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"telemetry/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"telemetry-limited/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry-full/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"newsletters/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"newsletters/2024-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2021-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"examples/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"examples/2020-06-credit-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-06-book-club/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-07-email-compose/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"keyboard-shortcuts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"limits/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"data-security/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"browser-support/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"glossary/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . title: Welcome to Grist # Welcome to Grist! # Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos. How-To Tutorials # Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity. Intro Videos # Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc Popular shortcuts # Frequently Asked Questions Function reference Keyboard shortcuts Contact us # If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Home"},{"location":"#title-welcome-to-grist","text":"","title":"title: Welcome to Grist"},{"location":"#welcome-to-grist","text":"Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos.","title":""},{"location":"#how-to-tutorials","text":"Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity.","title":"How-To Tutorials"},{"location":"#intro-videos","text":"Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc","title":"Intro Videos"},{"location":"#popular-shortcuts","text":"Frequently Asked Questions Function reference Keyboard shortcuts","title":"Popular shortcuts"},{"location":"#contact-us","text":"If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Contact us"},{"location":"FAQ/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Frequently Asked Questions # Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app? Accounts # Can I add multiple teams to the same Grist login account? # Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams. Can I add multiple login accounts to Grist? # Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I update my profile settings? # Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate. How can I change the email address I use for Grist? # It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I delete my account? # You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here . Plans # Why do I have multiple sites? # All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access. How to manage ownership of my team site? # Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other. Can I edit my team\u2019s name and subdomain? # You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 . Documents and data # Can I move documents between sites? # Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents . How many rows can I have? # As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits . Does Grist accept non-English characters? # Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas. How do I sum the total of a column? # To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables. Sharing # What\u2019s the difference between a team member and a guest? # Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price. Can I only share Grist documents with my team? # There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how . Grist and your website/app # Can I embed Grist into my website? # Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"lightweight-crm/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"investment-research/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"afterschool-program/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"creating-doc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"copying-docs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"imports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"exports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"automatic-backups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"document-history/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"workspaces/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"enter-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"page-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"raw-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"search-sort-filter/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"widget-table/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"widget-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-form/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-chart/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-calendar/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-custom/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord

    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"linking-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"custom-layouts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"record-cards/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"summary-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"on-demand-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"col-types/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"col-refs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"conditional-formatting/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"timestamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"authorship/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"col-transform/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"formulas/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"references-lookups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"dates/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"formula-timer/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"python/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"formula-cheat-sheet/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"ai-assistant/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"teams/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"team-sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"access-rules/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"rest-api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.
    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"integrators/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"embedding/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"webhooks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . description: How to configure webhooks for some external integrations # Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#description-how-to-configure-webhooks-for-some-external-integrations","text":"","title":"description: How to configure webhooks for some external integrations"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"code/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/modules/grist_plugin_api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"self-managed/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"install/saml/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"install/oidc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/forwarded-headers/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/cloud-storage/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/grist-connect/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/aws-marketplace/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"telemetry/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"telemetry-limited/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry-full/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"newsletters/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"newsletters/2024-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2021-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"examples/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"examples/2020-06-credit-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-06-book-club/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-07-email-compose/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"keyboard-shortcuts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"limits/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"data-security/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"browser-support/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"glossary/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"}]} \ No newline at end of file diff --git a/it/sitemap.xml b/it/sitemap.xml index eec96c8a7..c92969507 100644 --- a/it/sitemap.xml +++ b/it/sitemap.xml @@ -2,697 +2,697 @@ https://support.getgrist.com/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/FAQ/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/lightweight-crm/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/investment-research/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/afterschool-program/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/creating-doc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/copying-docs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/imports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/exports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/automatic-backups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/document-history/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/workspaces/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/enter-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/page-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/raw-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/search-sort-filter/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-table/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-form/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-chart/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-calendar/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-custom/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/linking-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/custom-layouts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/record-cards/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/summary-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/on-demand-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-types/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-refs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/conditional-formatting/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/timestamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/authorship/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-transform/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formulas/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/references-lookups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/dates/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-timer/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/python/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-cheat-sheet/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/ai-assistant/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/teams/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/team-sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/access-rules/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/rest-api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/integrators/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/embedding/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/webhooks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/grist_plugin_api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/self-managed/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/saml/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/oidc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/forwarded-headers/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/cloud-storage/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/grist-connect/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/aws-marketplace/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-limited/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-full/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-credit-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-book-club/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-07-email-compose/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-08-invoices/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-09-payroll/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-10-print-labels/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-11-treasure-hunt/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-12-map/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-01-tasks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-03-leads/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-04-link-keys/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-05-reference-columns/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-06-timesheets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-07-auto-stamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-01-acl-memo/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-07-proposals-contracts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/keyboard-shortcuts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/limits/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/data-security/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/browser-support/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/glossary/ - 2024-10-03 + 2024-10-09 daily \ No newline at end of file diff --git a/it/sitemap.xml.gz b/it/sitemap.xml.gz index e9d12372d5b4201247df817323598e6455ab986f..b565d8bead4729be4c301daaf576a81c3c70be10 100644 GIT binary patch delta 1071 zcmV+~1kn5R2=xemABzYGe~$)d0{?SqbY*Q}a4vXlYyj1qO>g5i5J2zyD+1rgmgMXv zDI70)*?WQZycm;Xi7-D@l8WMg-;v@?)9$I4V#bFgrWEOc(##v$A`f3*gTH&BTwIFJ zn|9l5?kJjMUF@GXfBgOp9yec)pAJ*@h3Xye9Lw`&sCE2*Ww)E>d0SkifMeOF-0zAR z_)Xc}8h6e1X>CdA@=dXv|hn6>#-8;*Fruz2lZ$inu>xVki@)Jy! z+J7#-5TBBNVuehFFq=UrDV{3_g&9)vFy_=}l-j2z6IKpau*nETECXgHl-hx5yx#h+ zNR6AYv`M9-SIKlZ)f6~F44lwhrco*<-+t4Y83%4j`6?81PWe(sG$i(JjGVc_Vnv~V zBlZNdv)m}P4>Qh^d+@DtfWjN#>^xs@sll>dWvF<6@+2G4V79Ue&q-`q`=bxJ%&F2Z zv!`fLc)Xa24H(3ZwBltc7iDMgw=M*a*EGr144IVentP-omMH|x*S8cd6*5Jz6fj!Z zn1;vF^*V~?%XF5D7pgocOieVHyEF4lUgBvABOU76P7d{Kx#Ag-j zmosI5iab^YpOWOUKUT3>@pg@2iD;>H<9Ov@j#4{tie^dM(l4+X2rHCUJQ_~c1@a`fip^Zt0FF*-4~ivou~MJ0a@8gEqvLQQYvo4AOs9eC z7`X!!OOny}jB8$5o4=2K>RtRUH(m<)Gw`T?9(%H4Lz-Re{jOl}!V#0RMmQoDVl!q- z7Pu0#BsNX{;59x?7M3q7`4$zbTUTPMNG#5{`%1ufi30IKjNe$_v8qxd+Ug)C z-9y`{A=T5TjTTZZ-ruY7{$7pu_iDVqSL6M?8t?Dbcz>_P`+Jix0~wPq0}zu;10(@k zlW_wVe~tGWx!$K&45QrV?BDTj*BeYG>qM_q@DuVWr3)B8opb;{00lk?@TM^p(gFPl zF1{pZwA{!~Fyb{Qv5)%w6PGE*>|zCvC-OSFxNV_k!ZKxoVG=u4>|f9wEK4TJM_on; zP_bN!4%j_-LaPM!9+u@r;6hg=z@OoLk~M3?aZe#C0spd2XkKT#Dj|M=Whpxly{f`T z4l_3xc pnJK#}b*9ZJr@2su+eV?3y-%g!|1n)@{Z*(Q{{kRQrwv|P004Lf2zUSh delta 1075 zcmV-31kC&O2=oYlABzYG@0I>$0{?SqbY*Q}a4vXlYyj1qOLOBk41n+ZD>Avm)+4(~ zI*E6B*?VW&^A1eH632X~Bo)Q~zL4Tg)9$I44&Xx)Q;PHrCEx>XkxyS=gTHy9TwIFJ zx9zUEy`gB5b#ZvU{p0s<@NoO}@abvJzEHj4onv{v9c!I`e%bGrW!V*1Dd1FgDG&Q% zMt)QF*T#Lbd%Qh-y5ZM)fVaQ4*OO1?upXuGVahyBDc9XVb;z#Nok?N;v|o4m_#aqm zNcPY*-5sKqVt!h{fCw}l>Ix)f2R8O>u*BIyc@>4(E1Zh zmfC+Vz7U^(l46BSg)o~@C@G#Q2Zb3^@-XEzWR%*cCKFZ;SFp(lMJyv`CY0KNX}Vnd zuSkuXu(nC1qgTmvIM)<7LJShnuU1g|$c=RM2(O|Z+2~SCES^J|8xy-53 zFSDm;QFy$VnGG1kj%$K(mE)_CGuoN&_ z*_ejM()BWl=F4=Fix;h{E=Ba446#z5OMWr{pj1)q}Su|HO^S@C*}VU1|1b>np5V2)BdaE@k8+tM$v83`+tkx1$w1|482 z{0^g3#7Y>7U^bBbSv(p})&=q`w~EbN*8omVY7dGfbFtEpv2xWV^`ql(Bx~hH$3n-E z>lnEM6ibrP_>5a#*_yvkejZ%>E;n8Z`4jMes2&HhVndo;?ESuA@WK(3vqm@~7h(%$ zOBT2ivm`c6{@^t}%@)=#EBO``&bqaLNimYC!dY=C9QXN5b@FIPY&GdAP!;<{^*bMU z7hDyaL;vPH&0cC2jkFX`waQIO9|<>*=1~plgBsF%HKbqEkbYJ}dZ&i8S3}yVA#HUj zkoM4aYDo3;X`_Wyi}!mq-tX0TzgOe^UXAyAHQw*lc)wTU{a%gtJCh&-CX-VG9tzcX zzg6S?R*m-?lWqeSf3ElG6~iR=Is13K>-7ec$vV+175s#JOz8~9&qp1=_dtP90=#KT zg>*nafQv8587(*RBTRV7N$jJ3|HNgADZ5y~nXt^6V4THH75f)-2kVlF z@=@0j0#q#Lq64-EPiU3E-od)O2%PDn1o$(&PqJoh_&G!+ZQx(F3C-JVS0%*vur6f> zqE}V;h`c^J0BAe}3P}g={kqmCmpHow to manage ownership of my t

    Open the team site to which you want add a second owner.

  • -

    Click ‘Manage Users’ under the user menu by clicking on the profile icon in the top-right of Grist.

    +

    Click ‘Manage Team’ under the user menu by clicking on the profile icon in the top-right of Grist.

  • Add the new email address as Owner, and click Confirm.

    @@ -1065,7 +1065,7 @@

    How to manage ownership of my t

    Go to ‘Billing Account’ (also under the user menu) and add the new Owner as a Billing Manager.

  • -

    The new Owner should log in, open the team site, and visit ‘Manage Users’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

    +

    The new Owner should log in, open the team site, and visit ‘Manage Team’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

  • It is not possible to add a second owner to, or transfer ownership of, a personal account.

    diff --git a/pt/index.html b/pt/index.html index b42ae18e1..45e2088ea 100644 --- a/pt/index.html +++ b/pt/index.html @@ -1098,5 +1098,5 @@

    Contact us Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Users\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Users\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"lightweight-crm/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"investment-research/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"afterschool-program/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"creating-doc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"copying-docs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"imports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"exports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"automatic-backups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"document-history/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"workspaces/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"enter-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"page-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"raw-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"search-sort-filter/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"widget-table/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"widget-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-form/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-chart/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-calendar/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-custom/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord

    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"linking-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"custom-layouts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"record-cards/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"summary-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"on-demand-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"col-types/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"col-refs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"conditional-formatting/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"timestamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"authorship/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"col-transform/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"formulas/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"references-lookups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"dates/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"formula-timer/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"python/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"formula-cheat-sheet/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"ai-assistant/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"teams/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"team-sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"access-rules/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"rest-api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.
    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"integrators/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"embedding/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"webhooks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . description: How to configure webhooks for some external integrations # Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#description-how-to-configure-webhooks-for-some-external-integrations","text":"","title":"description: How to configure webhooks for some external integrations"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"code/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/modules/grist_plugin_api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"self-managed/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"install/saml/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"install/oidc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/forwarded-headers/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/cloud-storage/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/grist-connect/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/aws-marketplace/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"telemetry/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"telemetry-limited/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry-full/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"newsletters/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"newsletters/2024-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2021-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"examples/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"examples/2020-06-credit-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-06-book-club/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-07-email-compose/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"keyboard-shortcuts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"limits/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"data-security/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"browser-support/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"glossary/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . title: Welcome to Grist # Welcome to Grist! # Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos. How-To Tutorials # Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity. Intro Videos # Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc Popular shortcuts # Frequently Asked Questions Function reference Keyboard shortcuts Contact us # If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Home"},{"location":"#title-welcome-to-grist","text":"","title":"title: Welcome to Grist"},{"location":"#welcome-to-grist","text":"Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos.","title":""},{"location":"#how-to-tutorials","text":"Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity.","title":"How-To Tutorials"},{"location":"#intro-videos","text":"Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc","title":"Intro Videos"},{"location":"#popular-shortcuts","text":"Frequently Asked Questions Function reference Keyboard shortcuts","title":"Popular shortcuts"},{"location":"#contact-us","text":"If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Contact us"},{"location":"FAQ/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Frequently Asked Questions # Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app? Accounts # Can I add multiple teams to the same Grist login account? # Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams. Can I add multiple login accounts to Grist? # Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I update my profile settings? # Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate. How can I change the email address I use for Grist? # It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I delete my account? # You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here . Plans # Why do I have multiple sites? # All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access. How to manage ownership of my team site? # Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other. Can I edit my team\u2019s name and subdomain? # You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 . Documents and data # Can I move documents between sites? # Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents . How many rows can I have? # As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits . Does Grist accept non-English characters? # Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas. How do I sum the total of a column? # To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables. Sharing # What\u2019s the difference between a team member and a guest? # Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price. Can I only share Grist documents with my team? # There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how . Grist and your website/app # Can I embed Grist into my website? # Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"lightweight-crm/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"investment-research/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"afterschool-program/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"creating-doc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"copying-docs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"imports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"exports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"automatic-backups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"document-history/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"workspaces/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"enter-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"page-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"raw-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"search-sort-filter/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"widget-table/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"widget-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-form/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-chart/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-calendar/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-custom/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord

    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"linking-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"custom-layouts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"record-cards/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"summary-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"on-demand-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"col-types/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"col-refs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"conditional-formatting/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"timestamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"authorship/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"col-transform/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"formulas/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"references-lookups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"dates/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"formula-timer/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"python/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"formula-cheat-sheet/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"ai-assistant/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"teams/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"team-sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"access-rules/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"rest-api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.
    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"integrators/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"embedding/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"webhooks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . description: How to configure webhooks for some external integrations # Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#description-how-to-configure-webhooks-for-some-external-integrations","text":"","title":"description: How to configure webhooks for some external integrations"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"code/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/modules/grist_plugin_api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"self-managed/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"install/saml/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"install/oidc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/forwarded-headers/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/cloud-storage/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/grist-connect/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/aws-marketplace/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"telemetry/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"telemetry-limited/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry-full/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"newsletters/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"newsletters/2024-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2021-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"examples/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"examples/2020-06-credit-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-06-book-club/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-07-email-compose/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"keyboard-shortcuts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"limits/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"data-security/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"browser-support/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"glossary/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"}]} \ No newline at end of file diff --git a/pt/sitemap.xml b/pt/sitemap.xml index eec96c8a7..c92969507 100644 --- a/pt/sitemap.xml +++ b/pt/sitemap.xml @@ -2,697 +2,697 @@ https://support.getgrist.com/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/FAQ/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/lightweight-crm/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/investment-research/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/afterschool-program/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/creating-doc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/copying-docs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/imports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/exports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/automatic-backups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/document-history/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/workspaces/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/enter-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/page-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/raw-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/search-sort-filter/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-table/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-form/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-chart/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-calendar/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-custom/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/linking-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/custom-layouts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/record-cards/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/summary-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/on-demand-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-types/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-refs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/conditional-formatting/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/timestamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/authorship/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-transform/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formulas/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/references-lookups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/dates/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-timer/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/python/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-cheat-sheet/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/ai-assistant/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/teams/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/team-sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/access-rules/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/rest-api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/integrators/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/embedding/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/webhooks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/grist_plugin_api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/self-managed/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/saml/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/oidc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/forwarded-headers/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/cloud-storage/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/grist-connect/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/aws-marketplace/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-limited/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-full/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-credit-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-book-club/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-07-email-compose/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-08-invoices/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-09-payroll/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-10-print-labels/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-11-treasure-hunt/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-12-map/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-01-tasks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-03-leads/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-04-link-keys/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-05-reference-columns/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-06-timesheets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-07-auto-stamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-01-acl-memo/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-07-proposals-contracts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/keyboard-shortcuts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/limits/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/data-security/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/browser-support/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/glossary/ - 2024-10-03 + 2024-10-09 daily \ No newline at end of file diff --git a/pt/sitemap.xml.gz b/pt/sitemap.xml.gz index 641ee2b7370ddaf35187a0f56b259292fcbfa0df..760920825d6cbbad0f2fa61ad7c223ffee0ecf70 100644 GIT binary patch delta 1071 zcmV+~1kn5R2=xemABzYGevbxc0{?SqbY*Q}a4vXlYyj1qO>g5i5J2zyD+1rgmgMXv zDI70)*?WQZycm;Xi7-D@l8WMg-;v@?)9$I4V#bFgrWEOc(##v$A`f3*gTH&BTwIFJ zn|9l5?kJjMUF@GXfBgOp9yec)pAJ*@h3Xye9Lw`&sCE2*Ww)E>d0SkifMeOF-0zAR z_)Xc}8h6e1X>CdA@=dXv|hn6>#-8;*Fruz2lZ$inu>xVki@)Jy! z+J7#-5TBBNVuehFFq=UrDV{3_g&9)vFy_=}l-j2z6IKpau*nETECXgHl-hx5yx#h+ zNR6AYv`M9-SIKlZ)f6~F44lwhrco*<-+t4Y83%4j`6?81PWe(sG$i(JjGVc_Vnv~V zBlZNdv)m}P4>Qh^d+@DtfWjN#>^xs@sll>dWvF<6@+2G4V79Ue&q-`q`=bxJ%&F2Z zv!`fLc)Xa24H(3ZwBltc7iDMgw=M*a*EGr144IVentP-omMH|x*S8cd6*5Jz6fj!Z zn1;vF^*V~?%XF5D7pgocOieVHyEF4lUgBvABOU76P7d{Kx#Ag-j zmosI5iab^YpOWOUKUT3>@pg@2iD;>H<9Ov@j#4{tie^dM(l4+X2rHCUJQ_~c1@a`fip^Zt0FF*-4~ivou~MJ0a@8gEqvLQQYvo4AOs9eC z7`X!!OOny}jB8$5o4=2K>RtRUH(m<)Gw`T?9(%H4Lz-Re{jOl}!V#0RMmQoDVl!q- z7Pu0#BsNX{;59x?7M3q7`4$zbTUTPMNG#5{`%1ufi30IKjNe$_v8qxd+Ug)C z-9y`{A=T5TjTTZZ-ruY7{$7pu_iDVqSL6M?8t?Dbcz>_P`+Jix0~wPq0}zu;10(@k zlW_wVe~tGWx!$K&45QrV?BDTj*BeYG>qM_q@DuVWr3)B8opb;{00lk?@TM^p(gFPl zF1{pZwA{!~Fyb{Qv5)%w6PGE*>|zCvC-OSFxNV_k!ZKxoVG=u4>|f9wEK4TJM_on; zP_bN!4%j_-LaPM!9+u@r;6hg=z@OoLk~M3?aZe#C0spd2XkKT#Dj|M=Whpxly{f`T z4l_3xc pnJK#}b*9ZJr@2su+eV?3y-%g!|1n)@{Z*(Q{{kRQrwv|P0048~2zLMg delta 1075 zcmV-31kC&O2=oYlABzYG?v?�{?SqbY*Q}a4vXlYyj1qOLOBk41n+ZD>Avm)+4(~ zI*E6B*?VW&^A1eH632X~Bo)Q~zL4Tg)9$I44&Xx)Q;PHrCEx>XkxyS=gTHy9TwIFJ zx9zUEy`gB5b#ZvU{p0s<@NoO}@abvJzEHj4onv{v9c!I`e%bGrW!V*1Dd1FgDG&Q% zMt)QF*T#Lbd%Qh-y5ZM)fVaQ4*OO1?upXuGVahyBDc9XVb;z#Nok?N;v|o4m_#aqm zNcPY*-5sKqVt!h{fCw}l>Ix)f2R8O>u*BIyc@>4(E1Zh zmfC+Vz7U^(l46BSg)o~@C@G#Q2Zb3^@-XEzWR%*cCKFZ;SFp(lMJyv`CY0KNX}Vnd zuSkuXu(nC1qgTmvIM)<7LJShnuU1g|$c=RM2(O|Z+2~SCES^J|8xy-53 zFSDm;QFy$VnGG1kj%$K(mE)_CGuoN&_ z*_ejM()BWl=F4=Fix;h{E=Ba446#z5OMWr{pj1)q}Su|HO^S@C*}VU1|1b>np5V2)BdaE@k8+tM$v83`+tkx1$w1|482 z{0^g3#7Y>7U^bBbSv(p})&=q`w~EbN*8omVY7dGfbFtEpv2xWV^`ql(Bx~hH$3n-E z>lnEM6ibrP_>5a#*_yvkejZ%>E;n8Z`4jMes2&HhVndo;?ESuA@WK(3vqm@~7h(%$ zOBT2ivm`c6{@^t}%@)=#EBO``&bqaLNimYC!dY=C9QXN5b@FIPY&GdAP!;<{^*bMU z7hDyaL;vPH&0cC2jkFX`waQIO9|<>*=1~plgBsF%HKbqEkbYJ}dZ&i8S3}yVA#HUj zkoM4aYDo3;X`_Wyi}!mq-tX0TzgOe^UXAyAHQw*lc)wTU{a%gtJCh&-CX-VG9tzcX zzg6S?R*m-?lWqeSf3ElG6~iR=Is13K>-7ec$vV+175s#JOz8~9&qp1=_dtP90=#KT zg>*nafQv8587(*RBTRV7N$jJ3|HNgADZ5y~nXt^6V4THH75f)-2kVlF z@=@0j0#q#Lq64-EPiU3E-od)O2%PDn1o$(&PqJoh_&G!+ZQx(F3C-JVS0%*vur6f> zqE}V;h`c^J0BAe}3P}g={kqmCmp Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Users\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Users\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"access-rules/","text":"Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"afterschool-program/","text":"How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"ai-assistant/","text":"AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"api/","text":"Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.
    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"authorship/","text":"Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"automatic-backups/","text":"Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"browser-support/","text":"Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"col-refs/","text":"Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"col-transform/","text":"Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"col-types/","text":"Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"conditional-formatting/","text":"Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"copying-docs/","text":"Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"creating-doc/","text":"Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"custom-layouts/","text":"Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"data-security/","text":"Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"dates/","text":"Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"document-history/","text":"Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"embedding/","text":"Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"enter-data/","text":"Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"examples/","text":"More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"exports/","text":"Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"formula-cheat-sheet/","text":"Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"formula-timer/","text":"Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"formulas/","text":"Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"functions/","text":"/* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"glossary/","text":"Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"},{"location":"imports/","text":"Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"integrators/","text":"Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"investment-research/","text":"How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"keyboard-shortcuts/","text":"Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"lightweight-crm/","text":"How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"limits/","text":"Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"linking-widgets/","text":"Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"newsletters/","text":"Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"on-demand-tables/","text":"On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"page-widgets/","text":"Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"python/","text":"Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"raw-data/","text":"Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"record-cards/","text":"Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"references-lookups/","text":"Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"register-as-consultant/","text":"Register to be a Grist consultant # .creg-form { line-height: initial; } .form-group .control-label { font-weight: normal; } .creg-button { margin: 24px 0; background-color: #11b683; border: none; color: white; } .creg-button:hover { background-color: #009058; border: none; color: white; } #creg-submitted, #creg-error { display: none; margin-top: 40px; } Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out the information below, and we\u2019ll get in touch with you. Your Name: Email: Phone: Company: Website: Your technical background: Excel: None Beginner Intermediate Advanced SQL: None Beginner Intermediate Advanced Python: None Beginner Intermediate Advanced Javascript: None Beginner Intermediate Advanced Grist: None Beginner Intermediate Advanced Are you interested in receiving Grist training? Interested Submit Thank you for registering! We'll be in touch. Meanwhile, you are welcome to reach out to us with any questions, at support@getgrist.com . Could not submit the form. Try again function formDataToObj(formElem) { const formData = new FormData(formElem); const data = {}; for (const pair of formData.entries()) { if (typeof pair[1] === 'string') { data[pair[0]] = [pair[1]]; } } return data; } const form = document.getElementById('creg-form'); form.addEventListener('submit', (ev) => { ev.preventDefault(); const data = formDataToObj(form); const options = { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } }; window.fetch(form.action, options) .then(resp => { return resp.json() .then(body => { console.log(\"BODY\", body); if (resp.status !== 200) { throw new Error(\"Could not submit the form: \" + (body && body.error || \"unknown error\")); } console.log(\"Form submitted\", body); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-submitted').style.display = 'block'; }) }) .catch(err => { console.warn(\"ERROR\", err); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-error-message').textContent = String(err); document.getElementById('creg-error').style.display = 'block'; }); });","title":"Register to be a Grist consultant"},{"location":"register-as-consultant/#register-to-be-a-grist-consultant","text":".creg-form { line-height: initial; } .form-group .control-label { font-weight: normal; } .creg-button { margin: 24px 0; background-color: #11b683; border: none; color: white; } .creg-button:hover { background-color: #009058; border: none; color: white; } #creg-submitted, #creg-error { display: none; margin-top: 40px; } Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out the information below, and we\u2019ll get in touch with you. Your Name: Email: Phone: Company: Website: Your technical background: Excel: None Beginner Intermediate Advanced SQL: None Beginner Intermediate Advanced Python: None Beginner Intermediate Advanced Javascript: None Beginner Intermediate Advanced Grist: None Beginner Intermediate Advanced Are you interested in receiving Grist training? Interested Submit Thank you for registering! We'll be in touch. Meanwhile, you are welcome to reach out to us with any questions, at support@getgrist.com . Could not submit the form. Try again function formDataToObj(formElem) { const formData = new FormData(formElem); const data = {}; for (const pair of formData.entries()) { if (typeof pair[1] === 'string') { data[pair[0]] = [pair[1]]; } } return data; } const form = document.getElementById('creg-form'); form.addEventListener('submit', (ev) => { ev.preventDefault(); const data = formDataToObj(form); const options = { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } }; window.fetch(form.action, options) .then(resp => { return resp.json() .then(body => { console.log(\"BODY\", body); if (resp.status !== 200) { throw new Error(\"Could not submit the form: \" + (body && body.error || \"unknown error\")); } console.log(\"Form submitted\", body); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-submitted').style.display = 'block'; }) }) .catch(err => { console.warn(\"ERROR\", err); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-error-message').textContent = String(err); document.getElementById('creg-error').style.display = 'block'; }); });","title":"Register to be a Grist consultant"},{"location":"rest-api/","text":"Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"search-sort-filter/","text":"Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"self-managed/","text":"Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"sharing/","text":"Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"summary-tables/","text":"Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"team-sharing/","text":"Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"teams/","text":"Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"telemetry-full/","text":"Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"telemetry-limited/","text":"Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry/","text":"Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"timestamps/","text":"Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"webhooks/","text":"Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"widget-calendar/","text":"Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-card/","text":"Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-chart/","text":"Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-custom/","text":"Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"widget-form/","text":"Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-table/","text":"Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"workspaces/","text":"Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"code/","text":"Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/enums/GristData.GristObjCode/","text":"Enumeration: GristObjCode # GristData .GristObjCode Letter codes for CellValue types encoded as [code, args\u2026] tuples. Table of contents # Enumeration Members # Censored Date DateTime Dict Exception List LookUp Pending Reference ReferenceList Skip Unmarshallable Versions Enumeration Members # Censored # \u2022 Censored = \"C\" Date # \u2022 Date = \"d\" DateTime # \u2022 DateTime = \"D\" Dict # \u2022 Dict = \"O\" Exception # \u2022 Exception = \"E\" List # \u2022 List = \"L\" LookUp # \u2022 LookUp = \"l\" Pending # \u2022 Pending = \"P\" Reference # \u2022 Reference = \"R\" ReferenceList # \u2022 ReferenceList = \"r\" Skip # \u2022 Skip = \"S\" Unmarshallable # \u2022 Unmarshallable = \"U\" Versions # \u2022 Versions = \"V\"","title":"Enumeration: GristObjCode"},{"location":"code/enums/GristData.GristObjCode/#enumeration-gristobjcode","text":"GristData .GristObjCode Letter codes for CellValue types encoded as [code, args\u2026] tuples.","title":"Enumeration: GristObjCode"},{"location":"code/enums/GristData.GristObjCode/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/enums/GristData.GristObjCode/#enumeration-members","text":"Censored Date DateTime Dict Exception List LookUp Pending Reference ReferenceList Skip Unmarshallable Versions","title":"Enumeration Members"},{"location":"code/enums/GristData.GristObjCode/#enumeration-members_1","text":"","title":"Enumeration Members"},{"location":"code/enums/GristData.GristObjCode/#censored","text":"\u2022 Censored = \"C\"","title":"Censored"},{"location":"code/enums/GristData.GristObjCode/#date","text":"\u2022 Date = \"d\"","title":"Date"},{"location":"code/enums/GristData.GristObjCode/#datetime","text":"\u2022 DateTime = \"D\"","title":"DateTime"},{"location":"code/enums/GristData.GristObjCode/#dict","text":"\u2022 Dict = \"O\"","title":"Dict"},{"location":"code/enums/GristData.GristObjCode/#exception","text":"\u2022 Exception = \"E\"","title":"Exception"},{"location":"code/enums/GristData.GristObjCode/#list","text":"\u2022 List = \"L\"","title":"List"},{"location":"code/enums/GristData.GristObjCode/#lookup","text":"\u2022 LookUp = \"l\"","title":"LookUp"},{"location":"code/enums/GristData.GristObjCode/#pending","text":"\u2022 Pending = \"P\"","title":"Pending"},{"location":"code/enums/GristData.GristObjCode/#reference","text":"\u2022 Reference = \"R\"","title":"Reference"},{"location":"code/enums/GristData.GristObjCode/#referencelist","text":"\u2022 ReferenceList = \"r\"","title":"ReferenceList"},{"location":"code/enums/GristData.GristObjCode/#skip","text":"\u2022 Skip = \"S\"","title":"Skip"},{"location":"code/enums/GristData.GristObjCode/#unmarshallable","text":"\u2022 Unmarshallable = \"U\"","title":"Unmarshallable"},{"location":"code/enums/GristData.GristObjCode/#versions","text":"\u2022 Versions = \"V\"","title":"Versions"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/","text":"Interface: AddOrUpdateRecord # DocApiTypes .AddOrUpdateRecord JSON schema for api /record endpoint. Used in PUT method for adding or updating records. Table of contents # Properties # fields require Properties # fields # \u2022 Optional fields : Object The values we will place in particular columns, either overwriting values in an existing record, or setting initial values in a new record. Index signature # \u25aa [coldId: string ]: CellValue require # \u2022 require : { [coldId: string] : CellValue ; } & { id? : number } The values we expect to have in particular columns, either by matching with an existing record, or creating a new record.","title":"Interface: AddOrUpdateRecord"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#interface-addorupdaterecord","text":"DocApiTypes .AddOrUpdateRecord JSON schema for api /record endpoint. Used in PUT method for adding or updating records.","title":"Interface: AddOrUpdateRecord"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#properties","text":"fields require","title":"Properties"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#fields","text":"\u2022 Optional fields : Object The values we will place in particular columns, either overwriting values in an existing record, or setting initial values in a new record.","title":"fields"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#index-signature","text":"\u25aa [coldId: string ]: CellValue","title":"Index signature"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#require","text":"\u2022 require : { [coldId: string] : CellValue ; } & { id? : number } The values we expect to have in particular columns, either by matching with an existing record, or creating a new record.","title":"require"},{"location":"code/interfaces/DocApiTypes.MinimalRecord/","text":"Interface: MinimalRecord # DocApiTypes .MinimalRecord The row id of a record, without any of its content.","title":"Interface: MinimalRecord"},{"location":"code/interfaces/DocApiTypes.MinimalRecord/#interface-minimalrecord","text":"DocApiTypes .MinimalRecord The row id of a record, without any of its content.","title":"Interface: MinimalRecord"},{"location":"code/interfaces/DocApiTypes.NewRecord/","text":"Interface: NewRecord # DocApiTypes .NewRecord JSON schema for api /record endpoint. Used in POST method for adding new records. Table of contents # Properties # fields Properties # fields # \u2022 Optional fields : Object Initial values of cells in record. Optional, if not set cells are left blank. Index signature # \u25aa [coldId: string ]: CellValue","title":"Interface: NewRecord"},{"location":"code/interfaces/DocApiTypes.NewRecord/#interface-newrecord","text":"DocApiTypes .NewRecord JSON schema for api /record endpoint. Used in POST method for adding new records.","title":"Interface: NewRecord"},{"location":"code/interfaces/DocApiTypes.NewRecord/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/DocApiTypes.NewRecord/#properties","text":"fields","title":"Properties"},{"location":"code/interfaces/DocApiTypes.NewRecord/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/DocApiTypes.NewRecord/#fields","text":"\u2022 Optional fields : Object Initial values of cells in record. Optional, if not set cells are left blank.","title":"fields"},{"location":"code/interfaces/DocApiTypes.NewRecord/#index-signature","text":"\u25aa [coldId: string ]: CellValue","title":"Index signature"},{"location":"code/interfaces/DocApiTypes.Record/","text":"Interface: Record # DocApiTypes .Record JSON schema for api /record endpoint. Used in PATCH method for updating existing records.","title":"Interface: Record"},{"location":"code/interfaces/DocApiTypes.Record/#interface-record","text":"DocApiTypes .Record JSON schema for api /record endpoint. Used in PATCH method for updating existing records.","title":"Interface: Record"},{"location":"code/interfaces/DocApiTypes.RecordsPatch/","text":"Interface: RecordsPatch # DocApiTypes .RecordsPatch JSON schema for the body of api /record PATCH endpoint","title":"Interface: RecordsPatch"},{"location":"code/interfaces/DocApiTypes.RecordsPatch/#interface-recordspatch","text":"DocApiTypes .RecordsPatch JSON schema for the body of api /record PATCH endpoint","title":"Interface: RecordsPatch"},{"location":"code/interfaces/DocApiTypes.RecordsPost/","text":"Interface: RecordsPost # DocApiTypes .RecordsPost JSON schema for the body of api /record POST endpoint","title":"Interface: RecordsPost"},{"location":"code/interfaces/DocApiTypes.RecordsPost/#interface-recordspost","text":"DocApiTypes .RecordsPost JSON schema for the body of api /record POST endpoint","title":"Interface: RecordsPost"},{"location":"code/interfaces/DocApiTypes.RecordsPut/","text":"Interface: RecordsPut # DocApiTypes .RecordsPut JSON schema for the body of api /record PUT endpoint","title":"Interface: RecordsPut"},{"location":"code/interfaces/DocApiTypes.RecordsPut/#interface-recordsput","text":"DocApiTypes .RecordsPut JSON schema for the body of api /record PUT endpoint","title":"Interface: RecordsPut"},{"location":"code/interfaces/DocApiTypes.SqlPost/","text":"Interface: SqlPost # DocApiTypes .SqlPost JSON schema for the body of api /sql POST endpoint","title":"Interface: SqlPost"},{"location":"code/interfaces/DocApiTypes.SqlPost/#interface-sqlpost","text":"DocApiTypes .SqlPost JSON schema for the body of api /sql POST endpoint","title":"Interface: SqlPost"},{"location":"code/interfaces/DocApiTypes.TablePost/","text":"Interface: TablePost # DocApiTypes .TablePost Creating tables requires a list of columns. fields is not accepted because it\u2019s not generally sensible to set the metadata fields on new tables. Hierarchy # ColumnsPost \u21b3 TablePost","title":"Interface: TablePost"},{"location":"code/interfaces/DocApiTypes.TablePost/#interface-tablepost","text":"DocApiTypes .TablePost Creating tables requires a list of columns. fields is not accepted because it\u2019s not generally sensible to set the metadata fields on new tables.","title":"Interface: TablePost"},{"location":"code/interfaces/DocApiTypes.TablePost/#hierarchy","text":"ColumnsPost \u21b3 TablePost","title":"Hierarchy"},{"location":"code/interfaces/GristData.RowRecord/","text":"Interface: RowRecord # GristData .RowRecord Map of column ids to CellValue \u2019s.","title":"Interface: RowRecord"},{"location":"code/interfaces/GristData.RowRecord/#interface-rowrecord","text":"GristData .RowRecord Map of column ids to CellValue \u2019s.","title":"Interface: RowRecord"},{"location":"code/interfaces/GristData.RowRecords/","text":"Interface: RowRecords # GristData .RowRecords Map of column ids to CellValue arrays, where array indexes correspond to rows.","title":"Interface: RowRecords"},{"location":"code/interfaces/GristData.RowRecords/#interface-rowrecords","text":"GristData .RowRecords Map of column ids to CellValue arrays, where array indexes correspond to rows.","title":"Interface: RowRecords"},{"location":"code/interfaces/TableOperations.OpOptions/","text":"Interface: OpOptions # TableOperations .OpOptions General options for table operations. Hierarchy # OpOptions \u21b3 UpsertOptions Table of contents # Properties # parseStrings Properties # parseStrings # \u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true.","title":"Interface: OpOptions"},{"location":"code/interfaces/TableOperations.OpOptions/#interface-opoptions","text":"TableOperations .OpOptions General options for table operations.","title":"Interface: OpOptions"},{"location":"code/interfaces/TableOperations.OpOptions/#hierarchy","text":"OpOptions \u21b3 UpsertOptions","title":"Hierarchy"},{"location":"code/interfaces/TableOperations.OpOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/TableOperations.OpOptions/#properties","text":"parseStrings","title":"Properties"},{"location":"code/interfaces/TableOperations.OpOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/TableOperations.OpOptions/#parsestrings","text":"\u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true.","title":"parseStrings"},{"location":"code/interfaces/TableOperations.TableOperations/","text":"Interface: TableOperations # TableOperations .TableOperations Offer CRUD-style operations on a table. Table of contents # Methods # create destroy getTableId update upsert Methods # create # \u25b8 create ( records , options? ): Promise < MinimalRecord > Create a record or records. Parameters # Name Type records NewRecord options? OpOptions Returns # Promise < MinimalRecord > destroy # \u25b8 destroy ( recordIds ): Promise < void > Delete a record or records. Parameters # Name Type recordIds number | number [] Returns # Promise < void > getTableId # \u25b8 getTableId (): Promise < string > Determine the tableId of the table. Returns # Promise < string > update # \u25b8 update ( records , options? ): Promise < void > Update a record or records. Parameters # Name Type records Record | Record [] options? OpOptions Returns # Promise < void > upsert # \u25b8 upsert ( records , options? ): Promise < void > Add or update a record or records. Parameters # Name Type records AddOrUpdateRecord | AddOrUpdateRecord [] options? UpsertOptions Returns # Promise < void >","title":"Interface: TableOperations"},{"location":"code/interfaces/TableOperations.TableOperations/#interface-tableoperations","text":"TableOperations .TableOperations Offer CRUD-style operations on a table.","title":"Interface: TableOperations"},{"location":"code/interfaces/TableOperations.TableOperations/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/TableOperations.TableOperations/#methods","text":"create destroy getTableId update upsert","title":"Methods"},{"location":"code/interfaces/TableOperations.TableOperations/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/TableOperations.TableOperations/#create","text":"\u25b8 create ( records , options? ): Promise < MinimalRecord > Create a record or records.","title":"create"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters","text":"Name Type records NewRecord options? OpOptions","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns","text":"Promise < MinimalRecord >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#destroy","text":"\u25b8 destroy ( recordIds ): Promise < void > Delete a record or records.","title":"destroy"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters_1","text":"Name Type recordIds number | number []","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#gettableid","text":"\u25b8 getTableId (): Promise < string > Determine the tableId of the table.","title":"getTableId"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_2","text":"Promise < string >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#update","text":"\u25b8 update ( records , options? ): Promise < void > Update a record or records.","title":"update"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters_2","text":"Name Type records Record | Record [] options? OpOptions","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_3","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#upsert","text":"\u25b8 upsert ( records , options? ): Promise < void > Add or update a record or records.","title":"upsert"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters_3","text":"Name Type records AddOrUpdateRecord | AddOrUpdateRecord [] options? UpsertOptions","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_4","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/TableOperations.UpsertOptions/","text":"Interface: UpsertOptions # TableOperations .UpsertOptions Extra options for upserts. Hierarchy # OpOptions \u21b3 UpsertOptions Table of contents # Properties # add allowEmptyRequire onMany parseStrings update Properties # add # \u2022 Optional add : boolean Permit inserting a record. Defaults to true. allowEmptyRequire # \u2022 Optional allowEmptyRequire : boolean Allow \u201cwildcard\u201d operation. Defaults to false. onMany # \u2022 Optional onMany : \"all\" | \"none\" | \"first\" Whether to update none, one, or all matching records. Defaults to \u201cfirst\u201d. parseStrings # \u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true. Inherited from # OpOptions . parseStrings update # \u2022 Optional update : boolean Permit updating a record. Defaults to true.","title":"Interface: UpsertOptions"},{"location":"code/interfaces/TableOperations.UpsertOptions/#interface-upsertoptions","text":"TableOperations .UpsertOptions Extra options for upserts.","title":"Interface: UpsertOptions"},{"location":"code/interfaces/TableOperations.UpsertOptions/#hierarchy","text":"OpOptions \u21b3 UpsertOptions","title":"Hierarchy"},{"location":"code/interfaces/TableOperations.UpsertOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/TableOperations.UpsertOptions/#properties","text":"add allowEmptyRequire onMany parseStrings update","title":"Properties"},{"location":"code/interfaces/TableOperations.UpsertOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/TableOperations.UpsertOptions/#add","text":"\u2022 Optional add : boolean Permit inserting a record. Defaults to true.","title":"add"},{"location":"code/interfaces/TableOperations.UpsertOptions/#allowemptyrequire","text":"\u2022 Optional allowEmptyRequire : boolean Allow \u201cwildcard\u201d operation. Defaults to false.","title":"allowEmptyRequire"},{"location":"code/interfaces/TableOperations.UpsertOptions/#onmany","text":"\u2022 Optional onMany : \"all\" | \"none\" | \"first\" Whether to update none, one, or all matching records. Defaults to \u201cfirst\u201d.","title":"onMany"},{"location":"code/interfaces/TableOperations.UpsertOptions/#parsestrings","text":"\u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true.","title":"parseStrings"},{"location":"code/interfaces/TableOperations.UpsertOptions/#inherited-from","text":"OpOptions . parseStrings","title":"Inherited from"},{"location":"code/interfaces/TableOperations.UpsertOptions/#update","text":"\u2022 Optional update : boolean Permit updating a record. Defaults to true.","title":"update"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/","text":"Interface: AccessTokenOptions # grist-plugin-api .AccessTokenOptions Options when creating access tokens. Table of contents # Properties # readOnly Properties # readOnly # \u2022 Optional readOnly : boolean Restrict use of token to reading only","title":"Interface: AccessTokenOptions"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#interface-accesstokenoptions","text":"grist-plugin-api .AccessTokenOptions Options when creating access tokens.","title":"Interface: AccessTokenOptions"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#properties","text":"readOnly","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#readonly","text":"\u2022 Optional readOnly : boolean Restrict use of token to reading only","title":"readOnly"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/","text":"Interface: AccessTokenResult # grist-plugin-api .AccessTokenResult Access token information, including the token string itself, a base URL for API calls for which the access token can be used, and the time-to-live the token was created with. Table of contents # Properties # baseUrl token ttlMsecs Properties # baseUrl # \u2022 baseUrl : string The base url of the API for which the token can be used. Currently tokens are associated with a single document, so the base url will be something like https://..../api/docs/DOCID Access tokens currently only grant access to endpoints dealing with the internal content of a document (such as tables and cells) and not its metadata (such as the document name or who it is shared with). token # \u2022 token : string The token string, which can currently be provided in an api call as a query parameter called \u201cauth\u201d ttlMsecs # \u2022 ttlMsecs : number Number of milliseconds the access token will remain valid for after creation. This will be several minutes.","title":"Interface: AccessTokenResult"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#interface-accesstokenresult","text":"grist-plugin-api .AccessTokenResult Access token information, including the token string itself, a base URL for API calls for which the access token can be used, and the time-to-live the token was created with.","title":"Interface: AccessTokenResult"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#properties","text":"baseUrl token ttlMsecs","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#baseurl","text":"\u2022 baseUrl : string The base url of the API for which the token can be used. Currently tokens are associated with a single document, so the base url will be something like https://..../api/docs/DOCID Access tokens currently only grant access to endpoints dealing with the internal content of a document (such as tables and cells) and not its metadata (such as the document name or who it is shared with).","title":"baseUrl"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#token","text":"\u2022 token : string The token string, which can currently be provided in an api call as a query parameter called \u201cauth\u201d","title":"token"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#ttlmsecs","text":"\u2022 ttlMsecs : number Number of milliseconds the access token will remain valid for after creation. This will be several minutes.","title":"ttlMsecs"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/","text":"Interface: ColumnToMap # grist-plugin-api .ColumnToMap API definitions for CustomSection plugins. Table of contents # Properties # allowMultiple description name optional strictType title type Properties # allowMultiple # \u2022 Optional allowMultiple : boolean Allow multiple column assignment, the result will be list of mapped table column names. description # \u2022 Optional description : null | string Optional long description of a column (used as a help text in section mapping). name # \u2022 name : string Column name that Widget expects. Must be a valid JSON property name. optional # \u2022 Optional optional : boolean Mark column as optional all columns are required by default. strictType # \u2022 Optional strictType : boolean Match column type strictly, so \u201cAny\u201d will require \u201cAny\u201d and not any other type. title # \u2022 Optional title : null | string Title or short description of a column (used as a label in section mapping). type # \u2022 Optional type : string Column types (as comma separated list), by default \u201cAny\u201d, what means that any type is allowed (unless strictType is true).","title":"Interface: ColumnToMap"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#interface-columntomap","text":"grist-plugin-api .ColumnToMap API definitions for CustomSection plugins.","title":"Interface: ColumnToMap"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#properties","text":"allowMultiple description name optional strictType title type","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#allowmultiple","text":"\u2022 Optional allowMultiple : boolean Allow multiple column assignment, the result will be list of mapped table column names.","title":"allowMultiple"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#description","text":"\u2022 Optional description : null | string Optional long description of a column (used as a help text in section mapping).","title":"description"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#name","text":"\u2022 name : string Column name that Widget expects. Must be a valid JSON property name.","title":"name"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#optional","text":"\u2022 Optional optional : boolean Mark column as optional all columns are required by default.","title":"optional"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#stricttype","text":"\u2022 Optional strictType : boolean Match column type strictly, so \u201cAny\u201d will require \u201cAny\u201d and not any other type.","title":"strictType"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#title","text":"\u2022 Optional title : null | string Title or short description of a column (used as a label in section mapping).","title":"title"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#type","text":"\u2022 Optional type : string Column types (as comma separated list), by default \u201cAny\u201d, what means that any type is allowed (unless strictType is true).","title":"type"},{"location":"code/interfaces/grist_plugin_api.CursorPos/","text":"Interface: CursorPos # grist-plugin-api .CursorPos Represents the position of an active cursor on a page. Table of contents # Properties # fieldIndex linkingRowIds rowId rowIndex sectionId Properties # fieldIndex # \u2022 Optional fieldIndex : number The index of the selected field in the current view. linkingRowIds # \u2022 Optional linkingRowIds : UIRowId [] When in a linked section, CursorPos may include which rows in the controlling sections are selected: the rowId in the linking-source section, in that section\u2019s linking source, etc. rowId # \u2022 Optional rowId : UIRowId The rowId (value of the id column) of the current cursor position, or \u2018new\u2019 if the cursor is on a new row. rowIndex # \u2022 Optional rowIndex : number The index of the current row in the current view. sectionId # \u2022 Optional sectionId : number The id of a section that this cursor is in. Ignored when setting a cursor position for a particular view.","title":"Interface: CursorPos"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#interface-cursorpos","text":"grist-plugin-api .CursorPos Represents the position of an active cursor on a page.","title":"Interface: CursorPos"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#properties","text":"fieldIndex linkingRowIds rowId rowIndex sectionId","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#fieldindex","text":"\u2022 Optional fieldIndex : number The index of the selected field in the current view.","title":"fieldIndex"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#linkingrowids","text":"\u2022 Optional linkingRowIds : UIRowId [] When in a linked section, CursorPos may include which rows in the controlling sections are selected: the rowId in the linking-source section, in that section\u2019s linking source, etc.","title":"linkingRowIds"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#rowid","text":"\u2022 Optional rowId : UIRowId The rowId (value of the id column) of the current cursor position, or \u2018new\u2019 if the cursor is on a new row.","title":"rowId"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#rowindex","text":"\u2022 Optional rowIndex : number The index of the current row in the current view.","title":"rowIndex"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#sectionid","text":"\u2022 Optional sectionId : number The id of a section that this cursor is in. Ignored when setting a cursor position for a particular view.","title":"sectionId"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/","text":"Interface: CustomSectionAPI # grist-plugin-api .CustomSectionAPI Interface for the mapping of a custom widget. Table of contents # Methods # configure mappings Methods # configure # \u25b8 configure ( customOptions ): Promise < void > Initial request from a Custom Widget that wants to declare its requirements. Parameters # Name Type customOptions InteractionOptionsRequest Returns # Promise < void > mappings # \u25b8 mappings (): Promise < null | WidgetColumnMap > Returns current widget configuration (if requested through configuration method). Returns # Promise < null | WidgetColumnMap >","title":"Interface: CustomSectionAPI"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#interface-customsectionapi","text":"grist-plugin-api .CustomSectionAPI Interface for the mapping of a custom widget.","title":"Interface: CustomSectionAPI"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#methods","text":"configure mappings","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#configure","text":"\u25b8 configure ( customOptions ): Promise < void > Initial request from a Custom Widget that wants to declare its requirements.","title":"configure"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#parameters","text":"Name Type customOptions InteractionOptionsRequest","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#mappings","text":"\u25b8 mappings (): Promise < null | WidgetColumnMap > Returns current widget configuration (if requested through configuration method).","title":"mappings"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#returns_1","text":"Promise < null | WidgetColumnMap >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/","text":"Interface: FetchSelectedOptions # grist-plugin-api .FetchSelectedOptions Options for functions which fetch data from the selected table or record: onRecords onRecord fetchSelectedRecord fetchSelectedTable GristView.fetchSelectedRecord GristView.fetchSelectedTable The different methods have different default values for keepEncoded and format . Table of contents # Properties # format includeColumns keepEncoded Properties # format # \u2022 Optional format : \"columns\" | \"rows\" rows , the returned data will be an array of objects, one per row, with column names as keys. columns , the returned data will be an object with column names as keys, and arrays of values. includeColumns # \u2022 Optional includeColumns : \"shown\" | \"normal\" | \"all\" shown (default): return only columns that are explicitly shown in the right panel configuration of the widget. This is the only value that doesn\u2019t require full access. normal : return all \u2018normal\u2019 columns, regardless of whether the user has shown them. all : also return special invisible columns like manualSort and display helper columns. keepEncoded # \u2022 Optional keepEncoded : boolean true : the returned data will contain raw CellValue \u2019s. false : the values will be decoded, replacing e.g. ['D', timestamp] with a moment date.","title":"Interface: FetchSelectedOptions"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#interface-fetchselectedoptions","text":"grist-plugin-api .FetchSelectedOptions Options for functions which fetch data from the selected table or record: onRecords onRecord fetchSelectedRecord fetchSelectedTable GristView.fetchSelectedRecord GristView.fetchSelectedTable The different methods have different default values for keepEncoded and format .","title":"Interface: FetchSelectedOptions"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#properties","text":"format includeColumns keepEncoded","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#format","text":"\u2022 Optional format : \"columns\" | \"rows\" rows , the returned data will be an array of objects, one per row, with column names as keys. columns , the returned data will be an object with column names as keys, and arrays of values.","title":"format"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#includecolumns","text":"\u2022 Optional includeColumns : \"shown\" | \"normal\" | \"all\" shown (default): return only columns that are explicitly shown in the right panel configuration of the widget. This is the only value that doesn\u2019t require full access. normal : return all \u2018normal\u2019 columns, regardless of whether the user has shown them. all : also return special invisible columns like manualSort and display helper columns.","title":"includeColumns"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#keepencoded","text":"\u2022 Optional keepEncoded : boolean true : the returned data will contain raw CellValue \u2019s. false : the values will be decoded, replacing e.g. ['D', timestamp] with a moment date.","title":"keepEncoded"},{"location":"code/interfaces/grist_plugin_api.GristColumn/","text":"Interface: GristColumn # grist-plugin-api .GristColumn Metadata about a single column.","title":"Interface: GristColumn"},{"location":"code/interfaces/grist_plugin_api.GristColumn/#interface-gristcolumn","text":"grist-plugin-api .GristColumn Metadata about a single column.","title":"Interface: GristColumn"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/","text":"Interface: GristDocAPI # grist-plugin-api .GristDocAPI Allows getting information from and interacting with the Grist document to which a plugin or widget is attached. Table of contents # Methods # applyUserActions fetchTable getAccessToken getDocName listTables Methods # applyUserActions # \u25b8 applyUserActions ( actions , options? ): Promise < any > Applies an array of user actions. Parameters # Name Type actions any [][] options? any Returns # Promise < any > fetchTable # \u25b8 fetchTable ( tableId ): Promise < any > Returns a complete table of data as GristData.RowRecords , including the \u2018id\u2019 column. Do not modify the returned arrays in-place, especially if used directly (not over RPC). Parameters # Name Type tableId string Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options ): Promise < AccessTokenResult > Get a token for out-of-band access to the document. Parameters # Name Type options AccessTokenOptions Returns # Promise < AccessTokenResult > getDocName # \u25b8 getDocName (): Promise < string > Returns an identifier for the document. Returns # Promise < string > listTables # \u25b8 listTables (): Promise < string []> Returns a sorted list of table IDs. Returns # Promise < string []>","title":"Interface: GristDocAPI"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#interface-gristdocapi","text":"grist-plugin-api .GristDocAPI Allows getting information from and interacting with the Grist document to which a plugin or widget is attached.","title":"Interface: GristDocAPI"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#methods","text":"applyUserActions fetchTable getAccessToken getDocName listTables","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#applyuseractions","text":"\u25b8 applyUserActions ( actions , options? ): Promise < any > Applies an array of user actions.","title":"applyUserActions"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#parameters","text":"Name Type actions any [][] options? any","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#fetchtable","text":"\u25b8 fetchTable ( tableId ): Promise < any > Returns a complete table of data as GristData.RowRecords , including the \u2018id\u2019 column. Do not modify the returned arrays in-place, especially if used directly (not over RPC).","title":"fetchTable"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#parameters_1","text":"Name Type tableId string","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_1","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#getaccesstoken","text":"\u25b8 getAccessToken ( options ): Promise < AccessTokenResult > Get a token for out-of-band access to the document.","title":"getAccessToken"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#parameters_2","text":"Name Type options AccessTokenOptions","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_2","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#getdocname","text":"\u25b8 getDocName (): Promise < string > Returns an identifier for the document.","title":"getDocName"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_3","text":"Promise < string >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#listtables","text":"\u25b8 listTables (): Promise < string []> Returns a sorted list of table IDs.","title":"listTables"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_4","text":"Promise < string []>","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristTable/","text":"Interface: GristTable # grist-plugin-api .GristTable Metadata and data for a table.","title":"Interface: GristTable"},{"location":"code/interfaces/grist_plugin_api.GristTable/#interface-gristtable","text":"grist-plugin-api .GristTable Metadata and data for a table.","title":"Interface: GristTable"},{"location":"code/interfaces/grist_plugin_api.GristView/","text":"Interface: GristView # grist-plugin-api .GristView Interface for the data backing a single widget. Table of contents # Methods # allowSelectBy fetchSelectedRecord fetchSelectedTable setCursorPos setSelectedRows Methods # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Fetches selected record by its rowId . By default, options.keepEncoded is true . Parameters # Name Type rowId number options? FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Like GristDocAPI.fetchTable , but gets data for the custom section specifically, if there is any. By default, options.keepEncoded is true and format is columns . Parameters # Name Type options? FetchSelectedOptions Returns # Promise < any > setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"Interface: GristView"},{"location":"code/interfaces/grist_plugin_api.GristView/#interface-gristview","text":"grist-plugin-api .GristView Interface for the data backing a single widget.","title":"Interface: GristView"},{"location":"code/interfaces/grist_plugin_api.GristView/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.GristView/#methods","text":"allowSelectBy fetchSelectedRecord fetchSelectedTable setCursorPos setSelectedRows","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristView/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristView/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Fetches selected record by its rowId . By default, options.keepEncoded is true .","title":"fetchSelectedRecord"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters","text":"Name Type rowId number options? FetchSelectedOptions","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_1","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Like GristDocAPI.fetchTable , but gets data for the custom section specifically, if there is any. By default, options.keepEncoded is true and format is columns .","title":"fetchSelectedTable"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters_1","text":"Name Type options? FetchSelectedOptions","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters_2","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_3","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters_3","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_4","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/","text":"Interface: InteractionOptions # grist-plugin-api .InteractionOptions Widget configuration set and approved by Grist, sent as part of ready message. Table of contents # Properties # accessLevel Properties # accessLevel # \u2022 accessLevel : string Granted access level.","title":"Interface: InteractionOptions"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#interface-interactionoptions","text":"grist-plugin-api .InteractionOptions Widget configuration set and approved by Grist, sent as part of ready message.","title":"Interface: InteractionOptions"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#properties","text":"accessLevel","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#accesslevel","text":"\u2022 accessLevel : string Granted access level.","title":"accessLevel"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/","text":"Interface: InteractionOptionsRequest # grist-plugin-api .InteractionOptionsRequest Initial message sent by the CustomWidget with initial requirements. Table of contents # Properties # allowSelectBy columns hasCustomOptions requiredAccess Properties # allowSelectBy # \u2022 Optional allowSelectBy : boolean Show widget as linking source. columns # \u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget. hasCustomOptions # \u2022 Optional hasCustomOptions : boolean Instructs Grist to show additional menu options that will trigger onEditOptions callback, that Widget can use to show custom options screen. requiredAccess # \u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level.","title":"Interface: InteractionOptionsRequest"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#interface-interactionoptionsrequest","text":"grist-plugin-api .InteractionOptionsRequest Initial message sent by the CustomWidget with initial requirements.","title":"Interface: InteractionOptionsRequest"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#properties","text":"allowSelectBy columns hasCustomOptions requiredAccess","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#allowselectby","text":"\u2022 Optional allowSelectBy : boolean Show widget as linking source.","title":"allowSelectBy"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#columns","text":"\u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget.","title":"columns"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#hascustomoptions","text":"\u2022 Optional hasCustomOptions : boolean Instructs Grist to show additional menu options that will trigger onEditOptions callback, that Widget can use to show custom options screen.","title":"hasCustomOptions"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#requiredaccess","text":"\u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level.","title":"requiredAccess"},{"location":"code/interfaces/grist_plugin_api.ParseOptionSchema/","text":"Interface: ParseOptionSchema # grist-plugin-api .ParseOptionSchema ParseOptionSchema contains information for generaing parse options UI","title":"Interface: ParseOptionSchema"},{"location":"code/interfaces/grist_plugin_api.ParseOptionSchema/#interface-parseoptionschema","text":"grist-plugin-api .ParseOptionSchema ParseOptionSchema contains information for generaing parse options UI","title":"Interface: ParseOptionSchema"},{"location":"code/interfaces/grist_plugin_api.ParseOptions/","text":"Interface: ParseOptions # grist-plugin-api .ParseOptions ParseOptions contains parse options depending on plugin, number of rows, which is special option that can be used for any plugin and schema for generating parse options UI","title":"Interface: ParseOptions"},{"location":"code/interfaces/grist_plugin_api.ParseOptions/#interface-parseoptions","text":"grist-plugin-api .ParseOptions ParseOptions contains parse options depending on plugin, number of rows, which is special option that can be used for any plugin and schema for generating parse options UI","title":"Interface: ParseOptions"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/","text":"Interface: ReadyPayload # grist-plugin-api .ReadyPayload Options when initializing connection to Grist. Hierarchy # Omit < InteractionOptionsRequest , \"hasCustomOptions\" > \u21b3 ReadyPayload Table of contents # Properties # allowSelectBy columns onEditOptions requiredAccess Properties # allowSelectBy # \u2022 Optional allowSelectBy : boolean Show widget as linking source. Inherited from # Omit.allowSelectBy columns # \u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget. Inherited from # Omit.columns onEditOptions # \u2022 Optional onEditOptions : () => unknown Type declaration # \u25b8 (): unknown Handler that will be called by Grist to open additional configuration panel inside the Custom Widget. Returns # unknown requiredAccess # \u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level. Inherited from # Omit.requiredAccess","title":"Interface: ReadyPayload"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#interface-readypayload","text":"grist-plugin-api .ReadyPayload Options when initializing connection to Grist.","title":"Interface: ReadyPayload"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#hierarchy","text":"Omit < InteractionOptionsRequest , \"hasCustomOptions\" > \u21b3 ReadyPayload","title":"Hierarchy"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#properties","text":"allowSelectBy columns onEditOptions requiredAccess","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#allowselectby","text":"\u2022 Optional allowSelectBy : boolean Show widget as linking source.","title":"allowSelectBy"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#inherited-from","text":"Omit.allowSelectBy","title":"Inherited from"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#columns","text":"\u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget.","title":"columns"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#inherited-from_1","text":"Omit.columns","title":"Inherited from"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#oneditoptions","text":"\u2022 Optional onEditOptions : () => unknown","title":"onEditOptions"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#type-declaration","text":"\u25b8 (): unknown Handler that will be called by Grist to open additional configuration panel inside the Custom Widget.","title":"Type declaration"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#returns","text":"unknown","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#requiredaccess","text":"\u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level.","title":"requiredAccess"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#inherited-from_2","text":"Omit.requiredAccess","title":"Inherited from"},{"location":"code/interfaces/grist_plugin_api.RenderOptions/","text":"Interface: RenderOptions # grist-plugin-api .RenderOptions Options for the grist.render function.","title":"Interface: RenderOptions"},{"location":"code/interfaces/grist_plugin_api.RenderOptions/#interface-renderoptions","text":"grist-plugin-api .RenderOptions Options for the grist.render function.","title":"Interface: RenderOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/","text":"Interface: WidgetAPI # grist-plugin-api .WidgetAPI API to manage Custom Widget state. Table of contents # Methods # clearOptions getOption getOptions setOption setOptions Methods # clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void >","title":"Interface: WidgetAPI"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#interface-widgetapi","text":"grist-plugin-api .WidgetAPI API to manage Custom Widget state.","title":"Interface: WidgetAPI"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#methods","text":"clearOptions getOption getOptions setOption setOptions","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#parameters","text":"Name Type key string","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_1","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_2","text":"Promise < null | object >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#parameters_1","text":"Name Type key string value any","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_3","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#parameters_2","text":"Name Type options Object","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_4","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetColumnMap/","text":"Interface: WidgetColumnMap # grist-plugin-api .WidgetColumnMap Current columns mapping between viewFields in section and Custom widget.","title":"Interface: WidgetColumnMap"},{"location":"code/interfaces/grist_plugin_api.WidgetColumnMap/#interface-widgetcolumnmap","text":"grist-plugin-api .WidgetColumnMap Current columns mapping between viewFields in section and Custom widget.","title":"Interface: WidgetColumnMap"},{"location":"code/modules/DocApiTypes/","text":"Module: DocApiTypes # Table of contents # Interfaces # AddOrUpdateRecord MinimalRecord NewRecord Record RecordsPatch RecordsPost RecordsPut SqlPost TablePost","title":"Module: DocApiTypes"},{"location":"code/modules/DocApiTypes/#module-docapitypes","text":"","title":"Module: DocApiTypes"},{"location":"code/modules/DocApiTypes/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/DocApiTypes/#interfaces","text":"AddOrUpdateRecord MinimalRecord NewRecord Record RecordsPatch RecordsPost RecordsPut SqlPost TablePost","title":"Interfaces"},{"location":"code/modules/GristData/","text":"Module: GristData # Table of contents # Enumerations # GristObjCode Interfaces # RowRecord RowRecords Type Aliases # CellValue Type Aliases # CellValue # \u01ac CellValue : number | string | boolean | null | [ GristObjCode , \u2026unknown[]] Possible types of cell content. Each CellValue may either be a primitive (e.g. true , 123 , \"hello\" , null ) or a tuple (JavaScript Array) representing a Grist object. The first element of the tuple is a string character representing the object code. For example, [\"L\", \"foo\", \"bar\"] is a CellValue of a Choice List column, where \"L\" is the type, and \"foo\" and \"bar\" are the choices. Grist Object Types # Code Type L List, e.g. [\"L\", \"foo\", \"bar\"] or [\"L\", 1, 2] l LookUp, as [\"l\", value, options] O Dict, as [\"O\", {key: value, ...}] D DateTimes, as [\"D\", timestamp, timezone] , e.g. [\"D\", 1704945919, \"UTC\"] d Date, as [\"d\", timestamp] , e.g. [\"d\", 1704844800] C Censored, as [\"C\"] R Reference, as [\"R\", table_id, row_id] , e.g. [\"R\", \"People\", 17] r ReferenceList, as [\"r\", table_id, row_id_list] , e.g. [\"r\", \"People\", [1,2]] E Exception, as [\"E\", name, ...] , e.g. [\"E\", \"ValueError\"] P Pending, as [\"P\"] U Unmarshallable, as [\"U\", text_representation] V Version, as [\"V\", version_obj]","title":"Module: GristData"},{"location":"code/modules/GristData/#module-gristdata","text":"","title":"Module: GristData"},{"location":"code/modules/GristData/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/GristData/#enumerations","text":"GristObjCode","title":"Enumerations"},{"location":"code/modules/GristData/#interfaces","text":"RowRecord RowRecords","title":"Interfaces"},{"location":"code/modules/GristData/#type-aliases","text":"CellValue","title":"Type Aliases"},{"location":"code/modules/GristData/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/GristData/#cellvalue","text":"\u01ac CellValue : number | string | boolean | null | [ GristObjCode , \u2026unknown[]] Possible types of cell content. Each CellValue may either be a primitive (e.g. true , 123 , \"hello\" , null ) or a tuple (JavaScript Array) representing a Grist object. The first element of the tuple is a string character representing the object code. For example, [\"L\", \"foo\", \"bar\"] is a CellValue of a Choice List column, where \"L\" is the type, and \"foo\" and \"bar\" are the choices.","title":"CellValue"},{"location":"code/modules/GristData/#grist-object-types","text":"Code Type L List, e.g. [\"L\", \"foo\", \"bar\"] or [\"L\", 1, 2] l LookUp, as [\"l\", value, options] O Dict, as [\"O\", {key: value, ...}] D DateTimes, as [\"D\", timestamp, timezone] , e.g. [\"D\", 1704945919, \"UTC\"] d Date, as [\"d\", timestamp] , e.g. [\"d\", 1704844800] C Censored, as [\"C\"] R Reference, as [\"R\", table_id, row_id] , e.g. [\"R\", \"People\", 17] r ReferenceList, as [\"r\", table_id, row_id_list] , e.g. [\"r\", \"People\", [1,2]] E Exception, as [\"E\", name, ...] , e.g. [\"E\", \"ValueError\"] P Pending, as [\"P\"] U Unmarshallable, as [\"U\", text_representation] V Version, as [\"V\", version_obj]","title":"Grist Object Types"},{"location":"code/modules/TableOperations/","text":"Module: TableOperations # Table of contents # Interfaces # OpOptions TableOperations UpsertOptions","title":"Module: TableOperations"},{"location":"code/modules/TableOperations/#module-tableoperations","text":"","title":"Module: TableOperations"},{"location":"code/modules/TableOperations/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/TableOperations/#interfaces","text":"OpOptions TableOperations UpsertOptions","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/","text":"Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"examples/2020-06-book-club/","text":"Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-06-credit-card/","text":"Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-07-email-compose/","text":"Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"install/aws-marketplace/","text":"AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"install/cloud-storage/","text":"Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/example-docker-nginx/","text":"Example using Docker and NGINX # This Example is originally authored by Akito . # This is a complete Docker setup example for Grist. The following docker-compose.yml files are needed. You will need to adjust the environment variables to your needs. Requirements # You need to have the most recent Docker distribution including the docker compose extension installed. To prepare the host environment, create an empty directory, and within it do: sudo -u \"$(id -un 1000):$(id -un 1000)\" mkdir -p ./config/nginx/site-confs ./data ./database/data NGINX Reverse Proxy with automatic HTTPS # For automatic HTTPS to work, you first need to setup proper DNS entries for the server you are running this reverse proxy on. This reverse proxy is decoupled from Grist, in a separate docker-compose.yml , so you may conveniently provide additional backends to which it can route traffic - for example, Authelia for authentication. This setup uses SWAG, a Docker image that bundles the NGINX reverse proxy with useful services including TLS certificate generation and renewal. This is the docker-compose.yml file managing the NGINX instance. version: \"3.9\" services: letsencrypt: image: lscr.io/linuxserver/swag # NGINX with automatic HTTPS container_name: nginx-letsencrypt-master network_mode: \"host\" environment: - PUID=1000 # Optional Change - PGID=1000 # Optional Change - TZ=Europe/London # Change! - URL=mydomain.eu # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - SUBDOMAINS=grist,webhook.grist # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - VALIDATION=http - EMAIL=admin@mydomain.eu # Change! - ONLY_SUBDOMAINS=true - STAGING=false # Enable if testing! volumes: - ./config:/config restart: unless-stopped NGINX Configuration # The following configuration is to be placed in ./config/nginx/site-confs/grist.conf , to make the NGINX instance route to Grist properly. server { listen 443 ssl http2; listen [::]:443 ssl http2; # Adjust to your needs! server_name grist.mydomain.eu webhook.grist.mydomain.eu; # enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf; # enable for ldap auth (requires ldap-location.conf in the location block) #include /config/nginx/ldap-server.conf; # enable for Authelia (requires authelia-location.conf in the location block) #include /config/nginx/authelia-server.conf; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } Grist # This is the docker-compose.yml for the Grist backend. It contains the Grist app deployment, which is accompanied by a PostgreSQL database. # https://github.com/gristlabs/grist-core#using-grist version: \"3.9\" services: grist: image: gristlabs/grist:1.0.8 # Change! --> https://hub.docker.com/r/gristlabs/grist/tags container_name: grist user: \"1000\" # Optional Change env_file: - ./grist.env volumes: - ./data:/persist ports: - 127.0.0.1:3000:8080 depends_on: - database database: image: postgres:15-alpine container_name: grist_db user: \"1000\" # Optional Change env_file: - ./grist_db.env volumes: - ./database/data:/var/lib/postgresql/data Environment # The following .env files must be located in the same folder as the Grist docker-compose.yml . grist.env # # https://github.com/gristlabs/grist-core#environment-variables PORT=8080 APP_HOME_URL=https://grist.mydomain.eu GRIST_ALLOWED_HOSTS=webhook.grist.mydomain.eu # Replace with webhook target domains GRIST_DOMAIN=grist.mydomain.eu GRIST_SINGLE_ORG=myorg GRIST_HIDE_UI_ELEMENTS=billing GRIST_LIST_PUBLIC_SITES=false GRIST_MAX_UPLOAD_ATTACHMENT_MB=10 GRIST_MAX_UPLOAD_IMPORT_MB=300 GRIST_ORG_IN_PATH=false GRIST_PAGE_TITLE_SUFFIX=_blank GRIST_FORCE_LOGIN=true GRIST_SUPPORT_ANON=false GRIST_THROTTLE_CPU=true GRIST_SANDBOX_FLAVOR=gvisor PYTHON_VERSION=3 PYTHON_VERSION_ON_CREATION=3 # Database TYPEORM_DATABASE=grist TYPEORM_USERNAME=grist TYPEORM_HOST=grist_db TYPEORM_LOGGING=false TYPEORM_PASSWORD=mysupersecretpassword CHANGE THIS!!!! TYPEORM_PORT=5432 TYPEORM_TYPE=postgres grist_db.env # # https://hub.docker.com/_/postgres POSTGRES_DB=grist POSTGRES_USER=grist POSTGRES_PASSWORD=mysupersecretpassword CHANGE THIS!!!!","title":"Example using Docker and NGINX"},{"location":"install/example-docker-nginx/#example-using-docker-and-nginx","text":"","title":"Example using Docker and NGINX"},{"location":"install/example-docker-nginx/#this-example-is-originally-authored-by-akito","text":"This is a complete Docker setup example for Grist. The following docker-compose.yml files are needed. You will need to adjust the environment variables to your needs.","title":"This Example is originally authored by Akito."},{"location":"install/example-docker-nginx/#requirements","text":"You need to have the most recent Docker distribution including the docker compose extension installed. To prepare the host environment, create an empty directory, and within it do: sudo -u \"$(id -un 1000):$(id -un 1000)\" mkdir -p ./config/nginx/site-confs ./data ./database/data","title":"Requirements"},{"location":"install/example-docker-nginx/#nginx-reverse-proxy-with-automatic-https","text":"For automatic HTTPS to work, you first need to setup proper DNS entries for the server you are running this reverse proxy on. This reverse proxy is decoupled from Grist, in a separate docker-compose.yml , so you may conveniently provide additional backends to which it can route traffic - for example, Authelia for authentication. This setup uses SWAG, a Docker image that bundles the NGINX reverse proxy with useful services including TLS certificate generation and renewal. This is the docker-compose.yml file managing the NGINX instance. version: \"3.9\" services: letsencrypt: image: lscr.io/linuxserver/swag # NGINX with automatic HTTPS container_name: nginx-letsencrypt-master network_mode: \"host\" environment: - PUID=1000 # Optional Change - PGID=1000 # Optional Change - TZ=Europe/London # Change! - URL=mydomain.eu # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - SUBDOMAINS=grist,webhook.grist # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - VALIDATION=http - EMAIL=admin@mydomain.eu # Change! - ONLY_SUBDOMAINS=true - STAGING=false # Enable if testing! volumes: - ./config:/config restart: unless-stopped","title":"NGINX Reverse Proxy with automatic HTTPS"},{"location":"install/example-docker-nginx/#nginx-configuration","text":"The following configuration is to be placed in ./config/nginx/site-confs/grist.conf , to make the NGINX instance route to Grist properly. server { listen 443 ssl http2; listen [::]:443 ssl http2; # Adjust to your needs! server_name grist.mydomain.eu webhook.grist.mydomain.eu; # enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf; # enable for ldap auth (requires ldap-location.conf in the location block) #include /config/nginx/ldap-server.conf; # enable for Authelia (requires authelia-location.conf in the location block) #include /config/nginx/authelia-server.conf; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } }","title":"NGINX Configuration"},{"location":"install/example-docker-nginx/#grist","text":"This is the docker-compose.yml for the Grist backend. It contains the Grist app deployment, which is accompanied by a PostgreSQL database. # https://github.com/gristlabs/grist-core#using-grist version: \"3.9\" services: grist: image: gristlabs/grist:1.0.8 # Change! --> https://hub.docker.com/r/gristlabs/grist/tags container_name: grist user: \"1000\" # Optional Change env_file: - ./grist.env volumes: - ./data:/persist ports: - 127.0.0.1:3000:8080 depends_on: - database database: image: postgres:15-alpine container_name: grist_db user: \"1000\" # Optional Change env_file: - ./grist_db.env volumes: - ./database/data:/var/lib/postgresql/data","title":"Grist"},{"location":"install/example-docker-nginx/#environment","text":"The following .env files must be located in the same folder as the Grist docker-compose.yml .","title":"Environment"},{"location":"install/example-docker-nginx/#gristenv","text":"# https://github.com/gristlabs/grist-core#environment-variables PORT=8080 APP_HOME_URL=https://grist.mydomain.eu GRIST_ALLOWED_HOSTS=webhook.grist.mydomain.eu # Replace with webhook target domains GRIST_DOMAIN=grist.mydomain.eu GRIST_SINGLE_ORG=myorg GRIST_HIDE_UI_ELEMENTS=billing GRIST_LIST_PUBLIC_SITES=false GRIST_MAX_UPLOAD_ATTACHMENT_MB=10 GRIST_MAX_UPLOAD_IMPORT_MB=300 GRIST_ORG_IN_PATH=false GRIST_PAGE_TITLE_SUFFIX=_blank GRIST_FORCE_LOGIN=true GRIST_SUPPORT_ANON=false GRIST_THROTTLE_CPU=true GRIST_SANDBOX_FLAVOR=gvisor PYTHON_VERSION=3 PYTHON_VERSION_ON_CREATION=3 # Database TYPEORM_DATABASE=grist TYPEORM_USERNAME=grist TYPEORM_HOST=grist_db TYPEORM_LOGGING=false TYPEORM_PASSWORD=mysupersecretpassword CHANGE THIS!!!! TYPEORM_PORT=5432 TYPEORM_TYPE=postgres","title":"grist.env"},{"location":"install/example-docker-nginx/#grist_dbenv","text":"# https://hub.docker.com/_/postgres POSTGRES_DB=grist POSTGRES_USER=grist POSTGRES_PASSWORD=mysupersecretpassword CHANGE THIS!!!!","title":"grist_db.env"},{"location":"install/forwarded-headers/","text":"Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/grist-connect/","text":"GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/oidc/","text":"OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/saml/","text":"SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"newsletters/2020-05/","text":"May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-03/","text":"March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-04/","text":"April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-05/","text":"May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-06/","text":"June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-07/","text":"July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-08/","text":"August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-09/","text":"September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-10/","text":"October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-12/","text":"December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2022-01/","text":"January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-09/","text":"September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Welcome to Grist! # Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos. How-To Tutorials # Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity. Intro Videos # Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc Popular shortcuts # Frequently Asked Questions Function reference Keyboard shortcuts Contact us # If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Welcome to Grist"},{"location":"#welcome-to-grist","text":"Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos.","title":""},{"location":"#how-to-tutorials","text":"Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity.","title":"How-To Tutorials"},{"location":"#intro-videos","text":"Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc","title":"Intro Videos"},{"location":"#popular-shortcuts","text":"Frequently Asked Questions Function reference Keyboard shortcuts","title":"Popular shortcuts"},{"location":"#contact-us","text":"If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Contact us"},{"location":"FAQ/","text":"Frequently Asked Questions # Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app? Accounts # Can I add multiple teams to the same Grist login account? # Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams. Can I add multiple login accounts to Grist? # Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I update my profile settings? # Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate. How can I change the email address I use for Grist? # It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I delete my account? # You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here . Plans # Why do I have multiple sites? # All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access. How to manage ownership of my team site? # Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other. Can I edit my team\u2019s name and subdomain? # You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 . Documents and data # Can I move documents between sites? # Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents . How many rows can I have? # As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits . Does Grist accept non-English characters? # Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas. How do I sum the total of a column? # To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables. Sharing # What\u2019s the difference between a team member and a guest? # Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price. Can I only share Grist documents with my team? # There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how . Grist and your website/app # Can I embed Grist into my website? # Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"access-rules/","text":"Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"afterschool-program/","text":"How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"ai-assistant/","text":"AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"api/","text":"Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.

    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"authorship/","text":"Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"automatic-backups/","text":"Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"browser-support/","text":"Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"col-refs/","text":"Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"col-transform/","text":"Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"col-types/","text":"Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"conditional-formatting/","text":"Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"copying-docs/","text":"Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"creating-doc/","text":"Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"custom-layouts/","text":"Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"data-security/","text":"Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"dates/","text":"Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"document-history/","text":"Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"embedding/","text":"Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"enter-data/","text":"Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"examples/","text":"More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"exports/","text":"Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"formula-cheat-sheet/","text":"Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"formula-timer/","text":"Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"formulas/","text":"Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"functions/","text":"/* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"glossary/","text":"Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"},{"location":"imports/","text":"Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"integrators/","text":"Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"investment-research/","text":"How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"keyboard-shortcuts/","text":"Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"lightweight-crm/","text":"How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"limits/","text":"Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"linking-widgets/","text":"Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"newsletters/","text":"Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"on-demand-tables/","text":"On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"page-widgets/","text":"Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"python/","text":"Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"raw-data/","text":"Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"record-cards/","text":"Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"references-lookups/","text":"Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"register-as-consultant/","text":"Register to be a Grist consultant # .creg-form { line-height: initial; } .form-group .control-label { font-weight: normal; } .creg-button { margin: 24px 0; background-color: #11b683; border: none; color: white; } .creg-button:hover { background-color: #009058; border: none; color: white; } #creg-submitted, #creg-error { display: none; margin-top: 40px; } Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out the information below, and we\u2019ll get in touch with you. Your Name: Email: Phone: Company: Website: Your technical background: Excel: None Beginner Intermediate Advanced SQL: None Beginner Intermediate Advanced Python: None Beginner Intermediate Advanced Javascript: None Beginner Intermediate Advanced Grist: None Beginner Intermediate Advanced Are you interested in receiving Grist training? Interested Submit Thank you for registering! We'll be in touch. Meanwhile, you are welcome to reach out to us with any questions, at support@getgrist.com . Could not submit the form. Try again function formDataToObj(formElem) { const formData = new FormData(formElem); const data = {}; for (const pair of formData.entries()) { if (typeof pair[1] === 'string') { data[pair[0]] = [pair[1]]; } } return data; } const form = document.getElementById('creg-form'); form.addEventListener('submit', (ev) => { ev.preventDefault(); const data = formDataToObj(form); const options = { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } }; window.fetch(form.action, options) .then(resp => { return resp.json() .then(body => { console.log(\"BODY\", body); if (resp.status !== 200) { throw new Error(\"Could not submit the form: \" + (body && body.error || \"unknown error\")); } console.log(\"Form submitted\", body); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-submitted').style.display = 'block'; }) }) .catch(err => { console.warn(\"ERROR\", err); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-error-message').textContent = String(err); document.getElementById('creg-error').style.display = 'block'; }); });","title":"Register to be a Grist consultant"},{"location":"register-as-consultant/#register-to-be-a-grist-consultant","text":".creg-form { line-height: initial; } .form-group .control-label { font-weight: normal; } .creg-button { margin: 24px 0; background-color: #11b683; border: none; color: white; } .creg-button:hover { background-color: #009058; border: none; color: white; } #creg-submitted, #creg-error { display: none; margin-top: 40px; } Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out the information below, and we\u2019ll get in touch with you. Your Name: Email: Phone: Company: Website: Your technical background: Excel: None Beginner Intermediate Advanced SQL: None Beginner Intermediate Advanced Python: None Beginner Intermediate Advanced Javascript: None Beginner Intermediate Advanced Grist: None Beginner Intermediate Advanced Are you interested in receiving Grist training? Interested Submit Thank you for registering! We'll be in touch. Meanwhile, you are welcome to reach out to us with any questions, at support@getgrist.com . Could not submit the form. Try again function formDataToObj(formElem) { const formData = new FormData(formElem); const data = {}; for (const pair of formData.entries()) { if (typeof pair[1] === 'string') { data[pair[0]] = [pair[1]]; } } return data; } const form = document.getElementById('creg-form'); form.addEventListener('submit', (ev) => { ev.preventDefault(); const data = formDataToObj(form); const options = { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', } }; window.fetch(form.action, options) .then(resp => { return resp.json() .then(body => { console.log(\"BODY\", body); if (resp.status !== 200) { throw new Error(\"Could not submit the form: \" + (body && body.error || \"unknown error\")); } console.log(\"Form submitted\", body); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-submitted').style.display = 'block'; }) }) .catch(err => { console.warn(\"ERROR\", err); document.getElementById('creg-form').style.display = 'none'; document.getElementById('creg-error-message').textContent = String(err); document.getElementById('creg-error').style.display = 'block'; }); });","title":"Register to be a Grist consultant"},{"location":"rest-api/","text":"Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"search-sort-filter/","text":"Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"self-managed/","text":"Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"sharing/","text":"Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"summary-tables/","text":"Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"team-sharing/","text":"Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"teams/","text":"Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"telemetry-full/","text":"Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"telemetry-limited/","text":"Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry/","text":"Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"timestamps/","text":"Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"webhooks/","text":"Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"widget-calendar/","text":"Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-card/","text":"Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-chart/","text":"Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-custom/","text":"Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"widget-form/","text":"Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-table/","text":"Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"workspaces/","text":"Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"code/","text":"Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/enums/GristData.GristObjCode/","text":"Enumeration: GristObjCode # GristData .GristObjCode Letter codes for CellValue types encoded as [code, args\u2026] tuples. Table of contents # Enumeration Members # Censored Date DateTime Dict Exception List LookUp Pending Reference ReferenceList Skip Unmarshallable Versions Enumeration Members # Censored # \u2022 Censored = \"C\" Date # \u2022 Date = \"d\" DateTime # \u2022 DateTime = \"D\" Dict # \u2022 Dict = \"O\" Exception # \u2022 Exception = \"E\" List # \u2022 List = \"L\" LookUp # \u2022 LookUp = \"l\" Pending # \u2022 Pending = \"P\" Reference # \u2022 Reference = \"R\" ReferenceList # \u2022 ReferenceList = \"r\" Skip # \u2022 Skip = \"S\" Unmarshallable # \u2022 Unmarshallable = \"U\" Versions # \u2022 Versions = \"V\"","title":"Enumeration: GristObjCode"},{"location":"code/enums/GristData.GristObjCode/#enumeration-gristobjcode","text":"GristData .GristObjCode Letter codes for CellValue types encoded as [code, args\u2026] tuples.","title":"Enumeration: GristObjCode"},{"location":"code/enums/GristData.GristObjCode/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/enums/GristData.GristObjCode/#enumeration-members","text":"Censored Date DateTime Dict Exception List LookUp Pending Reference ReferenceList Skip Unmarshallable Versions","title":"Enumeration Members"},{"location":"code/enums/GristData.GristObjCode/#enumeration-members_1","text":"","title":"Enumeration Members"},{"location":"code/enums/GristData.GristObjCode/#censored","text":"\u2022 Censored = \"C\"","title":"Censored"},{"location":"code/enums/GristData.GristObjCode/#date","text":"\u2022 Date = \"d\"","title":"Date"},{"location":"code/enums/GristData.GristObjCode/#datetime","text":"\u2022 DateTime = \"D\"","title":"DateTime"},{"location":"code/enums/GristData.GristObjCode/#dict","text":"\u2022 Dict = \"O\"","title":"Dict"},{"location":"code/enums/GristData.GristObjCode/#exception","text":"\u2022 Exception = \"E\"","title":"Exception"},{"location":"code/enums/GristData.GristObjCode/#list","text":"\u2022 List = \"L\"","title":"List"},{"location":"code/enums/GristData.GristObjCode/#lookup","text":"\u2022 LookUp = \"l\"","title":"LookUp"},{"location":"code/enums/GristData.GristObjCode/#pending","text":"\u2022 Pending = \"P\"","title":"Pending"},{"location":"code/enums/GristData.GristObjCode/#reference","text":"\u2022 Reference = \"R\"","title":"Reference"},{"location":"code/enums/GristData.GristObjCode/#referencelist","text":"\u2022 ReferenceList = \"r\"","title":"ReferenceList"},{"location":"code/enums/GristData.GristObjCode/#skip","text":"\u2022 Skip = \"S\"","title":"Skip"},{"location":"code/enums/GristData.GristObjCode/#unmarshallable","text":"\u2022 Unmarshallable = \"U\"","title":"Unmarshallable"},{"location":"code/enums/GristData.GristObjCode/#versions","text":"\u2022 Versions = \"V\"","title":"Versions"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/","text":"Interface: AddOrUpdateRecord # DocApiTypes .AddOrUpdateRecord JSON schema for api /record endpoint. Used in PUT method for adding or updating records. Table of contents # Properties # fields require Properties # fields # \u2022 Optional fields : Object The values we will place in particular columns, either overwriting values in an existing record, or setting initial values in a new record. Index signature # \u25aa [coldId: string ]: CellValue require # \u2022 require : { [coldId: string] : CellValue ; } & { id? : number } The values we expect to have in particular columns, either by matching with an existing record, or creating a new record.","title":"Interface: AddOrUpdateRecord"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#interface-addorupdaterecord","text":"DocApiTypes .AddOrUpdateRecord JSON schema for api /record endpoint. Used in PUT method for adding or updating records.","title":"Interface: AddOrUpdateRecord"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#properties","text":"fields require","title":"Properties"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#fields","text":"\u2022 Optional fields : Object The values we will place in particular columns, either overwriting values in an existing record, or setting initial values in a new record.","title":"fields"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#index-signature","text":"\u25aa [coldId: string ]: CellValue","title":"Index signature"},{"location":"code/interfaces/DocApiTypes.AddOrUpdateRecord/#require","text":"\u2022 require : { [coldId: string] : CellValue ; } & { id? : number } The values we expect to have in particular columns, either by matching with an existing record, or creating a new record.","title":"require"},{"location":"code/interfaces/DocApiTypes.MinimalRecord/","text":"Interface: MinimalRecord # DocApiTypes .MinimalRecord The row id of a record, without any of its content.","title":"Interface: MinimalRecord"},{"location":"code/interfaces/DocApiTypes.MinimalRecord/#interface-minimalrecord","text":"DocApiTypes .MinimalRecord The row id of a record, without any of its content.","title":"Interface: MinimalRecord"},{"location":"code/interfaces/DocApiTypes.NewRecord/","text":"Interface: NewRecord # DocApiTypes .NewRecord JSON schema for api /record endpoint. Used in POST method for adding new records. Table of contents # Properties # fields Properties # fields # \u2022 Optional fields : Object Initial values of cells in record. Optional, if not set cells are left blank. Index signature # \u25aa [coldId: string ]: CellValue","title":"Interface: NewRecord"},{"location":"code/interfaces/DocApiTypes.NewRecord/#interface-newrecord","text":"DocApiTypes .NewRecord JSON schema for api /record endpoint. Used in POST method for adding new records.","title":"Interface: NewRecord"},{"location":"code/interfaces/DocApiTypes.NewRecord/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/DocApiTypes.NewRecord/#properties","text":"fields","title":"Properties"},{"location":"code/interfaces/DocApiTypes.NewRecord/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/DocApiTypes.NewRecord/#fields","text":"\u2022 Optional fields : Object Initial values of cells in record. Optional, if not set cells are left blank.","title":"fields"},{"location":"code/interfaces/DocApiTypes.NewRecord/#index-signature","text":"\u25aa [coldId: string ]: CellValue","title":"Index signature"},{"location":"code/interfaces/DocApiTypes.Record/","text":"Interface: Record # DocApiTypes .Record JSON schema for api /record endpoint. Used in PATCH method for updating existing records.","title":"Interface: Record"},{"location":"code/interfaces/DocApiTypes.Record/#interface-record","text":"DocApiTypes .Record JSON schema for api /record endpoint. Used in PATCH method for updating existing records.","title":"Interface: Record"},{"location":"code/interfaces/DocApiTypes.RecordsPatch/","text":"Interface: RecordsPatch # DocApiTypes .RecordsPatch JSON schema for the body of api /record PATCH endpoint","title":"Interface: RecordsPatch"},{"location":"code/interfaces/DocApiTypes.RecordsPatch/#interface-recordspatch","text":"DocApiTypes .RecordsPatch JSON schema for the body of api /record PATCH endpoint","title":"Interface: RecordsPatch"},{"location":"code/interfaces/DocApiTypes.RecordsPost/","text":"Interface: RecordsPost # DocApiTypes .RecordsPost JSON schema for the body of api /record POST endpoint","title":"Interface: RecordsPost"},{"location":"code/interfaces/DocApiTypes.RecordsPost/#interface-recordspost","text":"DocApiTypes .RecordsPost JSON schema for the body of api /record POST endpoint","title":"Interface: RecordsPost"},{"location":"code/interfaces/DocApiTypes.RecordsPut/","text":"Interface: RecordsPut # DocApiTypes .RecordsPut JSON schema for the body of api /record PUT endpoint","title":"Interface: RecordsPut"},{"location":"code/interfaces/DocApiTypes.RecordsPut/#interface-recordsput","text":"DocApiTypes .RecordsPut JSON schema for the body of api /record PUT endpoint","title":"Interface: RecordsPut"},{"location":"code/interfaces/DocApiTypes.SqlPost/","text":"Interface: SqlPost # DocApiTypes .SqlPost JSON schema for the body of api /sql POST endpoint","title":"Interface: SqlPost"},{"location":"code/interfaces/DocApiTypes.SqlPost/#interface-sqlpost","text":"DocApiTypes .SqlPost JSON schema for the body of api /sql POST endpoint","title":"Interface: SqlPost"},{"location":"code/interfaces/DocApiTypes.TablePost/","text":"Interface: TablePost # DocApiTypes .TablePost Creating tables requires a list of columns. fields is not accepted because it\u2019s not generally sensible to set the metadata fields on new tables. Hierarchy # ColumnsPost \u21b3 TablePost","title":"Interface: TablePost"},{"location":"code/interfaces/DocApiTypes.TablePost/#interface-tablepost","text":"DocApiTypes .TablePost Creating tables requires a list of columns. fields is not accepted because it\u2019s not generally sensible to set the metadata fields on new tables.","title":"Interface: TablePost"},{"location":"code/interfaces/DocApiTypes.TablePost/#hierarchy","text":"ColumnsPost \u21b3 TablePost","title":"Hierarchy"},{"location":"code/interfaces/GristData.RowRecord/","text":"Interface: RowRecord # GristData .RowRecord Map of column ids to CellValue \u2019s.","title":"Interface: RowRecord"},{"location":"code/interfaces/GristData.RowRecord/#interface-rowrecord","text":"GristData .RowRecord Map of column ids to CellValue \u2019s.","title":"Interface: RowRecord"},{"location":"code/interfaces/GristData.RowRecords/","text":"Interface: RowRecords # GristData .RowRecords Map of column ids to CellValue arrays, where array indexes correspond to rows.","title":"Interface: RowRecords"},{"location":"code/interfaces/GristData.RowRecords/#interface-rowrecords","text":"GristData .RowRecords Map of column ids to CellValue arrays, where array indexes correspond to rows.","title":"Interface: RowRecords"},{"location":"code/interfaces/TableOperations.OpOptions/","text":"Interface: OpOptions # TableOperations .OpOptions General options for table operations. Hierarchy # OpOptions \u21b3 UpsertOptions Table of contents # Properties # parseStrings Properties # parseStrings # \u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true.","title":"Interface: OpOptions"},{"location":"code/interfaces/TableOperations.OpOptions/#interface-opoptions","text":"TableOperations .OpOptions General options for table operations.","title":"Interface: OpOptions"},{"location":"code/interfaces/TableOperations.OpOptions/#hierarchy","text":"OpOptions \u21b3 UpsertOptions","title":"Hierarchy"},{"location":"code/interfaces/TableOperations.OpOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/TableOperations.OpOptions/#properties","text":"parseStrings","title":"Properties"},{"location":"code/interfaces/TableOperations.OpOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/TableOperations.OpOptions/#parsestrings","text":"\u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true.","title":"parseStrings"},{"location":"code/interfaces/TableOperations.TableOperations/","text":"Interface: TableOperations # TableOperations .TableOperations Offer CRUD-style operations on a table. Table of contents # Methods # create destroy getTableId update upsert Methods # create # \u25b8 create ( records , options? ): Promise < MinimalRecord > Create a record or records. Parameters # Name Type records NewRecord options? OpOptions Returns # Promise < MinimalRecord > destroy # \u25b8 destroy ( recordIds ): Promise < void > Delete a record or records. Parameters # Name Type recordIds number | number [] Returns # Promise < void > getTableId # \u25b8 getTableId (): Promise < string > Determine the tableId of the table. Returns # Promise < string > update # \u25b8 update ( records , options? ): Promise < void > Update a record or records. Parameters # Name Type records Record | Record [] options? OpOptions Returns # Promise < void > upsert # \u25b8 upsert ( records , options? ): Promise < void > Add or update a record or records. Parameters # Name Type records AddOrUpdateRecord | AddOrUpdateRecord [] options? UpsertOptions Returns # Promise < void >","title":"Interface: TableOperations"},{"location":"code/interfaces/TableOperations.TableOperations/#interface-tableoperations","text":"TableOperations .TableOperations Offer CRUD-style operations on a table.","title":"Interface: TableOperations"},{"location":"code/interfaces/TableOperations.TableOperations/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/TableOperations.TableOperations/#methods","text":"create destroy getTableId update upsert","title":"Methods"},{"location":"code/interfaces/TableOperations.TableOperations/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/TableOperations.TableOperations/#create","text":"\u25b8 create ( records , options? ): Promise < MinimalRecord > Create a record or records.","title":"create"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters","text":"Name Type records NewRecord options? OpOptions","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns","text":"Promise < MinimalRecord >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#destroy","text":"\u25b8 destroy ( recordIds ): Promise < void > Delete a record or records.","title":"destroy"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters_1","text":"Name Type recordIds number | number []","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#gettableid","text":"\u25b8 getTableId (): Promise < string > Determine the tableId of the table.","title":"getTableId"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_2","text":"Promise < string >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#update","text":"\u25b8 update ( records , options? ): Promise < void > Update a record or records.","title":"update"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters_2","text":"Name Type records Record | Record [] options? OpOptions","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_3","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/TableOperations.TableOperations/#upsert","text":"\u25b8 upsert ( records , options? ): Promise < void > Add or update a record or records.","title":"upsert"},{"location":"code/interfaces/TableOperations.TableOperations/#parameters_3","text":"Name Type records AddOrUpdateRecord | AddOrUpdateRecord [] options? UpsertOptions","title":"Parameters"},{"location":"code/interfaces/TableOperations.TableOperations/#returns_4","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/TableOperations.UpsertOptions/","text":"Interface: UpsertOptions # TableOperations .UpsertOptions Extra options for upserts. Hierarchy # OpOptions \u21b3 UpsertOptions Table of contents # Properties # add allowEmptyRequire onMany parseStrings update Properties # add # \u2022 Optional add : boolean Permit inserting a record. Defaults to true. allowEmptyRequire # \u2022 Optional allowEmptyRequire : boolean Allow \u201cwildcard\u201d operation. Defaults to false. onMany # \u2022 Optional onMany : \"all\" | \"none\" | \"first\" Whether to update none, one, or all matching records. Defaults to \u201cfirst\u201d. parseStrings # \u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true. Inherited from # OpOptions . parseStrings update # \u2022 Optional update : boolean Permit updating a record. Defaults to true.","title":"Interface: UpsertOptions"},{"location":"code/interfaces/TableOperations.UpsertOptions/#interface-upsertoptions","text":"TableOperations .UpsertOptions Extra options for upserts.","title":"Interface: UpsertOptions"},{"location":"code/interfaces/TableOperations.UpsertOptions/#hierarchy","text":"OpOptions \u21b3 UpsertOptions","title":"Hierarchy"},{"location":"code/interfaces/TableOperations.UpsertOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/TableOperations.UpsertOptions/#properties","text":"add allowEmptyRequire onMany parseStrings update","title":"Properties"},{"location":"code/interfaces/TableOperations.UpsertOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/TableOperations.UpsertOptions/#add","text":"\u2022 Optional add : boolean Permit inserting a record. Defaults to true.","title":"add"},{"location":"code/interfaces/TableOperations.UpsertOptions/#allowemptyrequire","text":"\u2022 Optional allowEmptyRequire : boolean Allow \u201cwildcard\u201d operation. Defaults to false.","title":"allowEmptyRequire"},{"location":"code/interfaces/TableOperations.UpsertOptions/#onmany","text":"\u2022 Optional onMany : \"all\" | \"none\" | \"first\" Whether to update none, one, or all matching records. Defaults to \u201cfirst\u201d.","title":"onMany"},{"location":"code/interfaces/TableOperations.UpsertOptions/#parsestrings","text":"\u2022 Optional parseStrings : boolean Whether to parse strings based on the column type. Defaults to true.","title":"parseStrings"},{"location":"code/interfaces/TableOperations.UpsertOptions/#inherited-from","text":"OpOptions . parseStrings","title":"Inherited from"},{"location":"code/interfaces/TableOperations.UpsertOptions/#update","text":"\u2022 Optional update : boolean Permit updating a record. Defaults to true.","title":"update"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/","text":"Interface: AccessTokenOptions # grist-plugin-api .AccessTokenOptions Options when creating access tokens. Table of contents # Properties # readOnly Properties # readOnly # \u2022 Optional readOnly : boolean Restrict use of token to reading only","title":"Interface: AccessTokenOptions"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#interface-accesstokenoptions","text":"grist-plugin-api .AccessTokenOptions Options when creating access tokens.","title":"Interface: AccessTokenOptions"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#properties","text":"readOnly","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenOptions/#readonly","text":"\u2022 Optional readOnly : boolean Restrict use of token to reading only","title":"readOnly"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/","text":"Interface: AccessTokenResult # grist-plugin-api .AccessTokenResult Access token information, including the token string itself, a base URL for API calls for which the access token can be used, and the time-to-live the token was created with. Table of contents # Properties # baseUrl token ttlMsecs Properties # baseUrl # \u2022 baseUrl : string The base url of the API for which the token can be used. Currently tokens are associated with a single document, so the base url will be something like https://..../api/docs/DOCID Access tokens currently only grant access to endpoints dealing with the internal content of a document (such as tables and cells) and not its metadata (such as the document name or who it is shared with). token # \u2022 token : string The token string, which can currently be provided in an api call as a query parameter called \u201cauth\u201d ttlMsecs # \u2022 ttlMsecs : number Number of milliseconds the access token will remain valid for after creation. This will be several minutes.","title":"Interface: AccessTokenResult"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#interface-accesstokenresult","text":"grist-plugin-api .AccessTokenResult Access token information, including the token string itself, a base URL for API calls for which the access token can be used, and the time-to-live the token was created with.","title":"Interface: AccessTokenResult"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#properties","text":"baseUrl token ttlMsecs","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#baseurl","text":"\u2022 baseUrl : string The base url of the API for which the token can be used. Currently tokens are associated with a single document, so the base url will be something like https://..../api/docs/DOCID Access tokens currently only grant access to endpoints dealing with the internal content of a document (such as tables and cells) and not its metadata (such as the document name or who it is shared with).","title":"baseUrl"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#token","text":"\u2022 token : string The token string, which can currently be provided in an api call as a query parameter called \u201cauth\u201d","title":"token"},{"location":"code/interfaces/grist_plugin_api.AccessTokenResult/#ttlmsecs","text":"\u2022 ttlMsecs : number Number of milliseconds the access token will remain valid for after creation. This will be several minutes.","title":"ttlMsecs"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/","text":"Interface: ColumnToMap # grist-plugin-api .ColumnToMap API definitions for CustomSection plugins. Table of contents # Properties # allowMultiple description name optional strictType title type Properties # allowMultiple # \u2022 Optional allowMultiple : boolean Allow multiple column assignment, the result will be list of mapped table column names. description # \u2022 Optional description : null | string Optional long description of a column (used as a help text in section mapping). name # \u2022 name : string Column name that Widget expects. Must be a valid JSON property name. optional # \u2022 Optional optional : boolean Mark column as optional all columns are required by default. strictType # \u2022 Optional strictType : boolean Match column type strictly, so \u201cAny\u201d will require \u201cAny\u201d and not any other type. title # \u2022 Optional title : null | string Title or short description of a column (used as a label in section mapping). type # \u2022 Optional type : string Column types (as comma separated list), by default \u201cAny\u201d, what means that any type is allowed (unless strictType is true).","title":"Interface: ColumnToMap"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#interface-columntomap","text":"grist-plugin-api .ColumnToMap API definitions for CustomSection plugins.","title":"Interface: ColumnToMap"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#properties","text":"allowMultiple description name optional strictType title type","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#allowmultiple","text":"\u2022 Optional allowMultiple : boolean Allow multiple column assignment, the result will be list of mapped table column names.","title":"allowMultiple"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#description","text":"\u2022 Optional description : null | string Optional long description of a column (used as a help text in section mapping).","title":"description"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#name","text":"\u2022 name : string Column name that Widget expects. Must be a valid JSON property name.","title":"name"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#optional","text":"\u2022 Optional optional : boolean Mark column as optional all columns are required by default.","title":"optional"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#stricttype","text":"\u2022 Optional strictType : boolean Match column type strictly, so \u201cAny\u201d will require \u201cAny\u201d and not any other type.","title":"strictType"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#title","text":"\u2022 Optional title : null | string Title or short description of a column (used as a label in section mapping).","title":"title"},{"location":"code/interfaces/grist_plugin_api.ColumnToMap/#type","text":"\u2022 Optional type : string Column types (as comma separated list), by default \u201cAny\u201d, what means that any type is allowed (unless strictType is true).","title":"type"},{"location":"code/interfaces/grist_plugin_api.CursorPos/","text":"Interface: CursorPos # grist-plugin-api .CursorPos Represents the position of an active cursor on a page. Table of contents # Properties # fieldIndex linkingRowIds rowId rowIndex sectionId Properties # fieldIndex # \u2022 Optional fieldIndex : number The index of the selected field in the current view. linkingRowIds # \u2022 Optional linkingRowIds : UIRowId [] When in a linked section, CursorPos may include which rows in the controlling sections are selected: the rowId in the linking-source section, in that section\u2019s linking source, etc. rowId # \u2022 Optional rowId : UIRowId The rowId (value of the id column) of the current cursor position, or \u2018new\u2019 if the cursor is on a new row. rowIndex # \u2022 Optional rowIndex : number The index of the current row in the current view. sectionId # \u2022 Optional sectionId : number The id of a section that this cursor is in. Ignored when setting a cursor position for a particular view.","title":"Interface: CursorPos"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#interface-cursorpos","text":"grist-plugin-api .CursorPos Represents the position of an active cursor on a page.","title":"Interface: CursorPos"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#properties","text":"fieldIndex linkingRowIds rowId rowIndex sectionId","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#fieldindex","text":"\u2022 Optional fieldIndex : number The index of the selected field in the current view.","title":"fieldIndex"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#linkingrowids","text":"\u2022 Optional linkingRowIds : UIRowId [] When in a linked section, CursorPos may include which rows in the controlling sections are selected: the rowId in the linking-source section, in that section\u2019s linking source, etc.","title":"linkingRowIds"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#rowid","text":"\u2022 Optional rowId : UIRowId The rowId (value of the id column) of the current cursor position, or \u2018new\u2019 if the cursor is on a new row.","title":"rowId"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#rowindex","text":"\u2022 Optional rowIndex : number The index of the current row in the current view.","title":"rowIndex"},{"location":"code/interfaces/grist_plugin_api.CursorPos/#sectionid","text":"\u2022 Optional sectionId : number The id of a section that this cursor is in. Ignored when setting a cursor position for a particular view.","title":"sectionId"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/","text":"Interface: CustomSectionAPI # grist-plugin-api .CustomSectionAPI Interface for the mapping of a custom widget. Table of contents # Methods # configure mappings Methods # configure # \u25b8 configure ( customOptions ): Promise < void > Initial request from a Custom Widget that wants to declare its requirements. Parameters # Name Type customOptions InteractionOptionsRequest Returns # Promise < void > mappings # \u25b8 mappings (): Promise < null | WidgetColumnMap > Returns current widget configuration (if requested through configuration method). Returns # Promise < null | WidgetColumnMap >","title":"Interface: CustomSectionAPI"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#interface-customsectionapi","text":"grist-plugin-api .CustomSectionAPI Interface for the mapping of a custom widget.","title":"Interface: CustomSectionAPI"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#methods","text":"configure mappings","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#configure","text":"\u25b8 configure ( customOptions ): Promise < void > Initial request from a Custom Widget that wants to declare its requirements.","title":"configure"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#parameters","text":"Name Type customOptions InteractionOptionsRequest","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#mappings","text":"\u25b8 mappings (): Promise < null | WidgetColumnMap > Returns current widget configuration (if requested through configuration method).","title":"mappings"},{"location":"code/interfaces/grist_plugin_api.CustomSectionAPI/#returns_1","text":"Promise < null | WidgetColumnMap >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/","text":"Interface: FetchSelectedOptions # grist-plugin-api .FetchSelectedOptions Options for functions which fetch data from the selected table or record: onRecords onRecord fetchSelectedRecord fetchSelectedTable GristView.fetchSelectedRecord GristView.fetchSelectedTable The different methods have different default values for keepEncoded and format . Table of contents # Properties # format includeColumns keepEncoded Properties # format # \u2022 Optional format : \"columns\" | \"rows\" rows , the returned data will be an array of objects, one per row, with column names as keys. columns , the returned data will be an object with column names as keys, and arrays of values. includeColumns # \u2022 Optional includeColumns : \"shown\" | \"normal\" | \"all\" shown (default): return only columns that are explicitly shown in the right panel configuration of the widget. This is the only value that doesn\u2019t require full access. normal : return all \u2018normal\u2019 columns, regardless of whether the user has shown them. all : also return special invisible columns like manualSort and display helper columns. keepEncoded # \u2022 Optional keepEncoded : boolean true : the returned data will contain raw CellValue \u2019s. false : the values will be decoded, replacing e.g. ['D', timestamp] with a moment date.","title":"Interface: FetchSelectedOptions"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#interface-fetchselectedoptions","text":"grist-plugin-api .FetchSelectedOptions Options for functions which fetch data from the selected table or record: onRecords onRecord fetchSelectedRecord fetchSelectedTable GristView.fetchSelectedRecord GristView.fetchSelectedTable The different methods have different default values for keepEncoded and format .","title":"Interface: FetchSelectedOptions"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#properties","text":"format includeColumns keepEncoded","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#format","text":"\u2022 Optional format : \"columns\" | \"rows\" rows , the returned data will be an array of objects, one per row, with column names as keys. columns , the returned data will be an object with column names as keys, and arrays of values.","title":"format"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#includecolumns","text":"\u2022 Optional includeColumns : \"shown\" | \"normal\" | \"all\" shown (default): return only columns that are explicitly shown in the right panel configuration of the widget. This is the only value that doesn\u2019t require full access. normal : return all \u2018normal\u2019 columns, regardless of whether the user has shown them. all : also return special invisible columns like manualSort and display helper columns.","title":"includeColumns"},{"location":"code/interfaces/grist_plugin_api.FetchSelectedOptions/#keepencoded","text":"\u2022 Optional keepEncoded : boolean true : the returned data will contain raw CellValue \u2019s. false : the values will be decoded, replacing e.g. ['D', timestamp] with a moment date.","title":"keepEncoded"},{"location":"code/interfaces/grist_plugin_api.GristColumn/","text":"Interface: GristColumn # grist-plugin-api .GristColumn Metadata about a single column.","title":"Interface: GristColumn"},{"location":"code/interfaces/grist_plugin_api.GristColumn/#interface-gristcolumn","text":"grist-plugin-api .GristColumn Metadata about a single column.","title":"Interface: GristColumn"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/","text":"Interface: GristDocAPI # grist-plugin-api .GristDocAPI Allows getting information from and interacting with the Grist document to which a plugin or widget is attached. Table of contents # Methods # applyUserActions fetchTable getAccessToken getDocName listTables Methods # applyUserActions # \u25b8 applyUserActions ( actions , options? ): Promise < any > Applies an array of user actions. Parameters # Name Type actions any [][] options? any Returns # Promise < any > fetchTable # \u25b8 fetchTable ( tableId ): Promise < any > Returns a complete table of data as GristData.RowRecords , including the \u2018id\u2019 column. Do not modify the returned arrays in-place, especially if used directly (not over RPC). Parameters # Name Type tableId string Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options ): Promise < AccessTokenResult > Get a token for out-of-band access to the document. Parameters # Name Type options AccessTokenOptions Returns # Promise < AccessTokenResult > getDocName # \u25b8 getDocName (): Promise < string > Returns an identifier for the document. Returns # Promise < string > listTables # \u25b8 listTables (): Promise < string []> Returns a sorted list of table IDs. Returns # Promise < string []>","title":"Interface: GristDocAPI"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#interface-gristdocapi","text":"grist-plugin-api .GristDocAPI Allows getting information from and interacting with the Grist document to which a plugin or widget is attached.","title":"Interface: GristDocAPI"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#methods","text":"applyUserActions fetchTable getAccessToken getDocName listTables","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#applyuseractions","text":"\u25b8 applyUserActions ( actions , options? ): Promise < any > Applies an array of user actions.","title":"applyUserActions"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#parameters","text":"Name Type actions any [][] options? any","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#fetchtable","text":"\u25b8 fetchTable ( tableId ): Promise < any > Returns a complete table of data as GristData.RowRecords , including the \u2018id\u2019 column. Do not modify the returned arrays in-place, especially if used directly (not over RPC).","title":"fetchTable"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#parameters_1","text":"Name Type tableId string","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_1","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#getaccesstoken","text":"\u25b8 getAccessToken ( options ): Promise < AccessTokenResult > Get a token for out-of-band access to the document.","title":"getAccessToken"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#parameters_2","text":"Name Type options AccessTokenOptions","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_2","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#getdocname","text":"\u25b8 getDocName (): Promise < string > Returns an identifier for the document.","title":"getDocName"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_3","text":"Promise < string >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#listtables","text":"\u25b8 listTables (): Promise < string []> Returns a sorted list of table IDs.","title":"listTables"},{"location":"code/interfaces/grist_plugin_api.GristDocAPI/#returns_4","text":"Promise < string []>","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristTable/","text":"Interface: GristTable # grist-plugin-api .GristTable Metadata and data for a table.","title":"Interface: GristTable"},{"location":"code/interfaces/grist_plugin_api.GristTable/#interface-gristtable","text":"grist-plugin-api .GristTable Metadata and data for a table.","title":"Interface: GristTable"},{"location":"code/interfaces/grist_plugin_api.GristView/","text":"Interface: GristView # grist-plugin-api .GristView Interface for the data backing a single widget. Table of contents # Methods # allowSelectBy fetchSelectedRecord fetchSelectedTable setCursorPos setSelectedRows Methods # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Fetches selected record by its rowId . By default, options.keepEncoded is true . Parameters # Name Type rowId number options? FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Like GristDocAPI.fetchTable , but gets data for the custom section specifically, if there is any. By default, options.keepEncoded is true and format is columns . Parameters # Name Type options? FetchSelectedOptions Returns # Promise < any > setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"Interface: GristView"},{"location":"code/interfaces/grist_plugin_api.GristView/#interface-gristview","text":"grist-plugin-api .GristView Interface for the data backing a single widget.","title":"Interface: GristView"},{"location":"code/interfaces/grist_plugin_api.GristView/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.GristView/#methods","text":"allowSelectBy fetchSelectedRecord fetchSelectedTable setCursorPos setSelectedRows","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristView/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.GristView/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Fetches selected record by its rowId . By default, options.keepEncoded is true .","title":"fetchSelectedRecord"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters","text":"Name Type rowId number options? FetchSelectedOptions","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_1","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Like GristDocAPI.fetchTable , but gets data for the custom section specifically, if there is any. By default, options.keepEncoded is true and format is columns .","title":"fetchSelectedTable"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters_1","text":"Name Type options? FetchSelectedOptions","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters_2","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_3","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.GristView/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/interfaces/grist_plugin_api.GristView/#parameters_3","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.GristView/#returns_4","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/","text":"Interface: InteractionOptions # grist-plugin-api .InteractionOptions Widget configuration set and approved by Grist, sent as part of ready message. Table of contents # Properties # accessLevel Properties # accessLevel # \u2022 accessLevel : string Granted access level.","title":"Interface: InteractionOptions"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#interface-interactionoptions","text":"grist-plugin-api .InteractionOptions Widget configuration set and approved by Grist, sent as part of ready message.","title":"Interface: InteractionOptions"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#properties","text":"accessLevel","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptions/#accesslevel","text":"\u2022 accessLevel : string Granted access level.","title":"accessLevel"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/","text":"Interface: InteractionOptionsRequest # grist-plugin-api .InteractionOptionsRequest Initial message sent by the CustomWidget with initial requirements. Table of contents # Properties # allowSelectBy columns hasCustomOptions requiredAccess Properties # allowSelectBy # \u2022 Optional allowSelectBy : boolean Show widget as linking source. columns # \u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget. hasCustomOptions # \u2022 Optional hasCustomOptions : boolean Instructs Grist to show additional menu options that will trigger onEditOptions callback, that Widget can use to show custom options screen. requiredAccess # \u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level.","title":"Interface: InteractionOptionsRequest"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#interface-interactionoptionsrequest","text":"grist-plugin-api .InteractionOptionsRequest Initial message sent by the CustomWidget with initial requirements.","title":"Interface: InteractionOptionsRequest"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#properties","text":"allowSelectBy columns hasCustomOptions requiredAccess","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#allowselectby","text":"\u2022 Optional allowSelectBy : boolean Show widget as linking source.","title":"allowSelectBy"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#columns","text":"\u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget.","title":"columns"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#hascustomoptions","text":"\u2022 Optional hasCustomOptions : boolean Instructs Grist to show additional menu options that will trigger onEditOptions callback, that Widget can use to show custom options screen.","title":"hasCustomOptions"},{"location":"code/interfaces/grist_plugin_api.InteractionOptionsRequest/#requiredaccess","text":"\u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level.","title":"requiredAccess"},{"location":"code/interfaces/grist_plugin_api.ParseOptionSchema/","text":"Interface: ParseOptionSchema # grist-plugin-api .ParseOptionSchema ParseOptionSchema contains information for generaing parse options UI","title":"Interface: ParseOptionSchema"},{"location":"code/interfaces/grist_plugin_api.ParseOptionSchema/#interface-parseoptionschema","text":"grist-plugin-api .ParseOptionSchema ParseOptionSchema contains information for generaing parse options UI","title":"Interface: ParseOptionSchema"},{"location":"code/interfaces/grist_plugin_api.ParseOptions/","text":"Interface: ParseOptions # grist-plugin-api .ParseOptions ParseOptions contains parse options depending on plugin, number of rows, which is special option that can be used for any plugin and schema for generating parse options UI","title":"Interface: ParseOptions"},{"location":"code/interfaces/grist_plugin_api.ParseOptions/#interface-parseoptions","text":"grist-plugin-api .ParseOptions ParseOptions contains parse options depending on plugin, number of rows, which is special option that can be used for any plugin and schema for generating parse options UI","title":"Interface: ParseOptions"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/","text":"Interface: ReadyPayload # grist-plugin-api .ReadyPayload Options when initializing connection to Grist. Hierarchy # Omit < InteractionOptionsRequest , \"hasCustomOptions\" > \u21b3 ReadyPayload Table of contents # Properties # allowSelectBy columns onEditOptions requiredAccess Properties # allowSelectBy # \u2022 Optional allowSelectBy : boolean Show widget as linking source. Inherited from # Omit.allowSelectBy columns # \u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget. Inherited from # Omit.columns onEditOptions # \u2022 Optional onEditOptions : () => unknown Type declaration # \u25b8 (): unknown Handler that will be called by Grist to open additional configuration panel inside the Custom Widget. Returns # unknown requiredAccess # \u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level. Inherited from # Omit.requiredAccess","title":"Interface: ReadyPayload"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#interface-readypayload","text":"grist-plugin-api .ReadyPayload Options when initializing connection to Grist.","title":"Interface: ReadyPayload"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#hierarchy","text":"Omit < InteractionOptionsRequest , \"hasCustomOptions\" > \u21b3 ReadyPayload","title":"Hierarchy"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#properties","text":"allowSelectBy columns onEditOptions requiredAccess","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#properties_1","text":"","title":"Properties"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#allowselectby","text":"\u2022 Optional allowSelectBy : boolean Show widget as linking source.","title":"allowSelectBy"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#inherited-from","text":"Omit.allowSelectBy","title":"Inherited from"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#columns","text":"\u2022 Optional columns : ColumnsToMap Tells Grist what columns Custom Widget expects and allows user to map between existing column names and those requested by Custom Widget.","title":"columns"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#inherited-from_1","text":"Omit.columns","title":"Inherited from"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#oneditoptions","text":"\u2022 Optional onEditOptions : () => unknown","title":"onEditOptions"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#type-declaration","text":"\u25b8 (): unknown Handler that will be called by Grist to open additional configuration panel inside the Custom Widget.","title":"Type declaration"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#returns","text":"unknown","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#requiredaccess","text":"\u2022 Optional requiredAccess : string Required access level. If it wasn\u2019t granted already, Grist will prompt user to change the current access level.","title":"requiredAccess"},{"location":"code/interfaces/grist_plugin_api.ReadyPayload/#inherited-from_2","text":"Omit.requiredAccess","title":"Inherited from"},{"location":"code/interfaces/grist_plugin_api.RenderOptions/","text":"Interface: RenderOptions # grist-plugin-api .RenderOptions Options for the grist.render function.","title":"Interface: RenderOptions"},{"location":"code/interfaces/grist_plugin_api.RenderOptions/#interface-renderoptions","text":"grist-plugin-api .RenderOptions Options for the grist.render function.","title":"Interface: RenderOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/","text":"Interface: WidgetAPI # grist-plugin-api .WidgetAPI API to manage Custom Widget state. Table of contents # Methods # clearOptions getOption getOptions setOption setOptions Methods # clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void >","title":"Interface: WidgetAPI"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#interface-widgetapi","text":"grist-plugin-api .WidgetAPI API to manage Custom Widget state.","title":"Interface: WidgetAPI"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#methods","text":"clearOptions getOption getOptions setOption setOptions","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#methods_1","text":"","title":"Methods"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#parameters","text":"Name Type key string","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_1","text":"Promise < any >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_2","text":"Promise < null | object >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#parameters_1","text":"Name Type key string value any","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_3","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#parameters_2","text":"Name Type options Object","title":"Parameters"},{"location":"code/interfaces/grist_plugin_api.WidgetAPI/#returns_4","text":"Promise < void >","title":"Returns"},{"location":"code/interfaces/grist_plugin_api.WidgetColumnMap/","text":"Interface: WidgetColumnMap # grist-plugin-api .WidgetColumnMap Current columns mapping between viewFields in section and Custom widget.","title":"Interface: WidgetColumnMap"},{"location":"code/interfaces/grist_plugin_api.WidgetColumnMap/#interface-widgetcolumnmap","text":"grist-plugin-api .WidgetColumnMap Current columns mapping between viewFields in section and Custom widget.","title":"Interface: WidgetColumnMap"},{"location":"code/modules/DocApiTypes/","text":"Module: DocApiTypes # Table of contents # Interfaces # AddOrUpdateRecord MinimalRecord NewRecord Record RecordsPatch RecordsPost RecordsPut SqlPost TablePost","title":"Module: DocApiTypes"},{"location":"code/modules/DocApiTypes/#module-docapitypes","text":"","title":"Module: DocApiTypes"},{"location":"code/modules/DocApiTypes/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/DocApiTypes/#interfaces","text":"AddOrUpdateRecord MinimalRecord NewRecord Record RecordsPatch RecordsPost RecordsPut SqlPost TablePost","title":"Interfaces"},{"location":"code/modules/GristData/","text":"Module: GristData # Table of contents # Enumerations # GristObjCode Interfaces # RowRecord RowRecords Type Aliases # CellValue Type Aliases # CellValue # \u01ac CellValue : number | string | boolean | null | [ GristObjCode , \u2026unknown[]] Possible types of cell content. Each CellValue may either be a primitive (e.g. true , 123 , \"hello\" , null ) or a tuple (JavaScript Array) representing a Grist object. The first element of the tuple is a string character representing the object code. For example, [\"L\", \"foo\", \"bar\"] is a CellValue of a Choice List column, where \"L\" is the type, and \"foo\" and \"bar\" are the choices. Grist Object Types # Code Type L List, e.g. [\"L\", \"foo\", \"bar\"] or [\"L\", 1, 2] l LookUp, as [\"l\", value, options] O Dict, as [\"O\", {key: value, ...}] D DateTimes, as [\"D\", timestamp, timezone] , e.g. [\"D\", 1704945919, \"UTC\"] d Date, as [\"d\", timestamp] , e.g. [\"d\", 1704844800] C Censored, as [\"C\"] R Reference, as [\"R\", table_id, row_id] , e.g. [\"R\", \"People\", 17] r ReferenceList, as [\"r\", table_id, row_id_list] , e.g. [\"r\", \"People\", [1,2]] E Exception, as [\"E\", name, ...] , e.g. [\"E\", \"ValueError\"] P Pending, as [\"P\"] U Unmarshallable, as [\"U\", text_representation] V Version, as [\"V\", version_obj]","title":"Module: GristData"},{"location":"code/modules/GristData/#module-gristdata","text":"","title":"Module: GristData"},{"location":"code/modules/GristData/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/GristData/#enumerations","text":"GristObjCode","title":"Enumerations"},{"location":"code/modules/GristData/#interfaces","text":"RowRecord RowRecords","title":"Interfaces"},{"location":"code/modules/GristData/#type-aliases","text":"CellValue","title":"Type Aliases"},{"location":"code/modules/GristData/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/GristData/#cellvalue","text":"\u01ac CellValue : number | string | boolean | null | [ GristObjCode , \u2026unknown[]] Possible types of cell content. Each CellValue may either be a primitive (e.g. true , 123 , \"hello\" , null ) or a tuple (JavaScript Array) representing a Grist object. The first element of the tuple is a string character representing the object code. For example, [\"L\", \"foo\", \"bar\"] is a CellValue of a Choice List column, where \"L\" is the type, and \"foo\" and \"bar\" are the choices.","title":"CellValue"},{"location":"code/modules/GristData/#grist-object-types","text":"Code Type L List, e.g. [\"L\", \"foo\", \"bar\"] or [\"L\", 1, 2] l LookUp, as [\"l\", value, options] O Dict, as [\"O\", {key: value, ...}] D DateTimes, as [\"D\", timestamp, timezone] , e.g. [\"D\", 1704945919, \"UTC\"] d Date, as [\"d\", timestamp] , e.g. [\"d\", 1704844800] C Censored, as [\"C\"] R Reference, as [\"R\", table_id, row_id] , e.g. [\"R\", \"People\", 17] r ReferenceList, as [\"r\", table_id, row_id_list] , e.g. [\"r\", \"People\", [1,2]] E Exception, as [\"E\", name, ...] , e.g. [\"E\", \"ValueError\"] P Pending, as [\"P\"] U Unmarshallable, as [\"U\", text_representation] V Version, as [\"V\", version_obj]","title":"Grist Object Types"},{"location":"code/modules/TableOperations/","text":"Module: TableOperations # Table of contents # Interfaces # OpOptions TableOperations UpsertOptions","title":"Module: TableOperations"},{"location":"code/modules/TableOperations/#module-tableoperations","text":"","title":"Module: TableOperations"},{"location":"code/modules/TableOperations/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/TableOperations/#interfaces","text":"OpOptions TableOperations UpsertOptions","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/","text":"Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"examples/2020-06-book-club/","text":"Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-06-credit-card/","text":"Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-07-email-compose/","text":"Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"install/aws-marketplace/","text":"AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"install/cloud-storage/","text":"Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/example-docker-nginx/","text":"Example using Docker and NGINX # This Example is originally authored by Akito . # This is a complete Docker setup example for Grist. The following docker-compose.yml files are needed. You will need to adjust the environment variables to your needs. Requirements # You need to have the most recent Docker distribution including the docker compose extension installed. To prepare the host environment, create an empty directory, and within it do: sudo -u \"$(id -un 1000):$(id -un 1000)\" mkdir -p ./config/nginx/site-confs ./data ./database/data NGINX Reverse Proxy with automatic HTTPS # For automatic HTTPS to work, you first need to setup proper DNS entries for the server you are running this reverse proxy on. This reverse proxy is decoupled from Grist, in a separate docker-compose.yml , so you may conveniently provide additional backends to which it can route traffic - for example, Authelia for authentication. This setup uses SWAG, a Docker image that bundles the NGINX reverse proxy with useful services including TLS certificate generation and renewal. This is the docker-compose.yml file managing the NGINX instance. version: \"3.9\" services: letsencrypt: image: lscr.io/linuxserver/swag # NGINX with automatic HTTPS container_name: nginx-letsencrypt-master network_mode: \"host\" environment: - PUID=1000 # Optional Change - PGID=1000 # Optional Change - TZ=Europe/London # Change! - URL=mydomain.eu # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - SUBDOMAINS=grist,webhook.grist # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - VALIDATION=http - EMAIL=admin@mydomain.eu # Change! - ONLY_SUBDOMAINS=true - STAGING=false # Enable if testing! volumes: - ./config:/config restart: unless-stopped NGINX Configuration # The following configuration is to be placed in ./config/nginx/site-confs/grist.conf , to make the NGINX instance route to Grist properly. server { listen 443 ssl http2; listen [::]:443 ssl http2; # Adjust to your needs! server_name grist.mydomain.eu webhook.grist.mydomain.eu; # enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf; # enable for ldap auth (requires ldap-location.conf in the location block) #include /config/nginx/ldap-server.conf; # enable for Authelia (requires authelia-location.conf in the location block) #include /config/nginx/authelia-server.conf; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } Grist # This is the docker-compose.yml for the Grist backend. It contains the Grist app deployment, which is accompanied by a PostgreSQL database. # https://github.com/gristlabs/grist-core#using-grist version: \"3.9\" services: grist: image: gristlabs/grist:1.0.8 # Change! --> https://hub.docker.com/r/gristlabs/grist/tags container_name: grist user: \"1000\" # Optional Change env_file: - ./grist.env volumes: - ./data:/persist ports: - 127.0.0.1:3000:8080 depends_on: - database database: image: postgres:15-alpine container_name: grist_db user: \"1000\" # Optional Change env_file: - ./grist_db.env volumes: - ./database/data:/var/lib/postgresql/data Environment # The following .env files must be located in the same folder as the Grist docker-compose.yml . grist.env # # https://github.com/gristlabs/grist-core#environment-variables PORT=8080 APP_HOME_URL=https://grist.mydomain.eu GRIST_ALLOWED_HOSTS=webhook.grist.mydomain.eu # Replace with webhook target domains GRIST_DOMAIN=grist.mydomain.eu GRIST_SINGLE_ORG=myorg GRIST_HIDE_UI_ELEMENTS=billing GRIST_LIST_PUBLIC_SITES=false GRIST_MAX_UPLOAD_ATTACHMENT_MB=10 GRIST_MAX_UPLOAD_IMPORT_MB=300 GRIST_ORG_IN_PATH=false GRIST_PAGE_TITLE_SUFFIX=_blank GRIST_FORCE_LOGIN=true GRIST_SUPPORT_ANON=false GRIST_THROTTLE_CPU=true GRIST_SANDBOX_FLAVOR=gvisor PYTHON_VERSION=3 PYTHON_VERSION_ON_CREATION=3 # Database TYPEORM_DATABASE=grist TYPEORM_USERNAME=grist TYPEORM_HOST=grist_db TYPEORM_LOGGING=false TYPEORM_PASSWORD=mysupersecretpassword CHANGE THIS!!!! TYPEORM_PORT=5432 TYPEORM_TYPE=postgres grist_db.env # # https://hub.docker.com/_/postgres POSTGRES_DB=grist POSTGRES_USER=grist POSTGRES_PASSWORD=mysupersecretpassword CHANGE THIS!!!!","title":"Example using Docker and NGINX"},{"location":"install/example-docker-nginx/#example-using-docker-and-nginx","text":"","title":"Example using Docker and NGINX"},{"location":"install/example-docker-nginx/#this-example-is-originally-authored-by-akito","text":"This is a complete Docker setup example for Grist. The following docker-compose.yml files are needed. You will need to adjust the environment variables to your needs.","title":"This Example is originally authored by Akito."},{"location":"install/example-docker-nginx/#requirements","text":"You need to have the most recent Docker distribution including the docker compose extension installed. To prepare the host environment, create an empty directory, and within it do: sudo -u \"$(id -un 1000):$(id -un 1000)\" mkdir -p ./config/nginx/site-confs ./data ./database/data","title":"Requirements"},{"location":"install/example-docker-nginx/#nginx-reverse-proxy-with-automatic-https","text":"For automatic HTTPS to work, you first need to setup proper DNS entries for the server you are running this reverse proxy on. This reverse proxy is decoupled from Grist, in a separate docker-compose.yml , so you may conveniently provide additional backends to which it can route traffic - for example, Authelia for authentication. This setup uses SWAG, a Docker image that bundles the NGINX reverse proxy with useful services including TLS certificate generation and renewal. This is the docker-compose.yml file managing the NGINX instance. version: \"3.9\" services: letsencrypt: image: lscr.io/linuxserver/swag # NGINX with automatic HTTPS container_name: nginx-letsencrypt-master network_mode: \"host\" environment: - PUID=1000 # Optional Change - PGID=1000 # Optional Change - TZ=Europe/London # Change! - URL=mydomain.eu # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - SUBDOMAINS=grist,webhook.grist # Change here, in ./config/nginx/site-confs/grist.conf & in .env files! - VALIDATION=http - EMAIL=admin@mydomain.eu # Change! - ONLY_SUBDOMAINS=true - STAGING=false # Enable if testing! volumes: - ./config:/config restart: unless-stopped","title":"NGINX Reverse Proxy with automatic HTTPS"},{"location":"install/example-docker-nginx/#nginx-configuration","text":"The following configuration is to be placed in ./config/nginx/site-confs/grist.conf , to make the NGINX instance route to Grist properly. server { listen 443 ssl http2; listen [::]:443 ssl http2; # Adjust to your needs! server_name grist.mydomain.eu webhook.grist.mydomain.eu; # enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf; # enable for ldap auth (requires ldap-location.conf in the location block) #include /config/nginx/ldap-server.conf; # enable for Authelia (requires authelia-location.conf in the location block) #include /config/nginx/authelia-server.conf; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } }","title":"NGINX Configuration"},{"location":"install/example-docker-nginx/#grist","text":"This is the docker-compose.yml for the Grist backend. It contains the Grist app deployment, which is accompanied by a PostgreSQL database. # https://github.com/gristlabs/grist-core#using-grist version: \"3.9\" services: grist: image: gristlabs/grist:1.0.8 # Change! --> https://hub.docker.com/r/gristlabs/grist/tags container_name: grist user: \"1000\" # Optional Change env_file: - ./grist.env volumes: - ./data:/persist ports: - 127.0.0.1:3000:8080 depends_on: - database database: image: postgres:15-alpine container_name: grist_db user: \"1000\" # Optional Change env_file: - ./grist_db.env volumes: - ./database/data:/var/lib/postgresql/data","title":"Grist"},{"location":"install/example-docker-nginx/#environment","text":"The following .env files must be located in the same folder as the Grist docker-compose.yml .","title":"Environment"},{"location":"install/example-docker-nginx/#gristenv","text":"# https://github.com/gristlabs/grist-core#environment-variables PORT=8080 APP_HOME_URL=https://grist.mydomain.eu GRIST_ALLOWED_HOSTS=webhook.grist.mydomain.eu # Replace with webhook target domains GRIST_DOMAIN=grist.mydomain.eu GRIST_SINGLE_ORG=myorg GRIST_HIDE_UI_ELEMENTS=billing GRIST_LIST_PUBLIC_SITES=false GRIST_MAX_UPLOAD_ATTACHMENT_MB=10 GRIST_MAX_UPLOAD_IMPORT_MB=300 GRIST_ORG_IN_PATH=false GRIST_PAGE_TITLE_SUFFIX=_blank GRIST_FORCE_LOGIN=true GRIST_SUPPORT_ANON=false GRIST_THROTTLE_CPU=true GRIST_SANDBOX_FLAVOR=gvisor PYTHON_VERSION=3 PYTHON_VERSION_ON_CREATION=3 # Database TYPEORM_DATABASE=grist TYPEORM_USERNAME=grist TYPEORM_HOST=grist_db TYPEORM_LOGGING=false TYPEORM_PASSWORD=mysupersecretpassword CHANGE THIS!!!! TYPEORM_PORT=5432 TYPEORM_TYPE=postgres","title":"grist.env"},{"location":"install/example-docker-nginx/#grist_dbenv","text":"# https://hub.docker.com/_/postgres POSTGRES_DB=grist POSTGRES_USER=grist POSTGRES_PASSWORD=mysupersecretpassword CHANGE THIS!!!!","title":"grist_db.env"},{"location":"install/forwarded-headers/","text":"Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/grist-connect/","text":"GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/oidc/","text":"OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/saml/","text":"SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"newsletters/2020-05/","text":"May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-03/","text":"March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-04/","text":"April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-05/","text":"May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-06/","text":"June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-07/","text":"July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-08/","text":"August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-09/","text":"September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-10/","text":"October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-12/","text":"December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2022-01/","text":"January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-09/","text":"September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 6954f9111..ae125c247 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,882 +2,882 @@ https://support.getgrist.com/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/FAQ/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/access-rules/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/afterschool-program/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/ai-assistant/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/authorship/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/automatic-backups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/browser-support/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-refs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-transform/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-types/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/conditional-formatting/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/copying-docs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/creating-doc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/custom-layouts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/data-security/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/dates/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/document-history/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/embedding/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/enter-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/exports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-cheat-sheet/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-timer/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formulas/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/glossary/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/imports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/integrators/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/investment-research/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/keyboard-shortcuts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/lightweight-crm/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/limits/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/linking-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/on-demand-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/page-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/python/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/raw-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/record-cards/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/references-lookups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/register-as-consultant/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/rest-api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/search-sort-filter/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/self-managed/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/summary-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/team-sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/teams/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-full/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-limited/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/timestamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/webhooks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-calendar/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-chart/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-custom/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-form/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-table/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/workspaces/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/enums/GristData.GristObjCode/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.AddOrUpdateRecord/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.MinimalRecord/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.NewRecord/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.Record/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.RecordsPatch/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.RecordsPost/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.RecordsPut/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.SqlPost/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/DocApiTypes.TablePost/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/GristData.RowRecord/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/GristData.RowRecords/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/TableOperations.OpOptions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/TableOperations.TableOperations/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/TableOperations.UpsertOptions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.AccessTokenOptions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.AccessTokenResult/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.ColumnToMap/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.CursorPos/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.CustomSectionAPI/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.FetchSelectedOptions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.GristColumn/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.GristDocAPI/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.GristTable/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.GristView/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.InteractionOptions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.InteractionOptionsRequest/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.ParseOptionSchema/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.ParseOptions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.ReadyPayload/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.RenderOptions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.WidgetAPI/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/interfaces/grist_plugin_api.WidgetColumnMap/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/DocApiTypes/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/GristData/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/TableOperations/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/grist_plugin_api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-book-club/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-credit-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-07-email-compose/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-08-invoices/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-09-payroll/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-10-print-labels/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-11-treasure-hunt/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-12-map/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-01-tasks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-03-leads/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-04-link-keys/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-05-reference-columns/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-06-timesheets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-07-auto-stamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-01-acl-memo/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-07-proposals-contracts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/aws-marketplace/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/cloud-storage/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/example-docker-nginx/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/forwarded-headers/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/grist-connect/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/oidc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/saml/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-09/ - 2024-10-03 + 2024-10-09 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 983708c8cb7feff513e5cde87c404fefadeaa255..cffdec82df0aecb4d6923d2d94c7f7df91cfd0f0 100644 GIT binary patch delta 1233 zcmV;?1TOo>3&#rxABzYGcaH{VkqFCwXLQSB*|Xbp2d>3U#LpKE7`r>4KP%az%%52P zGu74QuT056YfMSS!D8ZyxXIMdbmzTQA^J7~*JcKzASHSSE^|c;hAY{SJ0DYL0$)Ji zLpEBiL2rk^b-b*j9}`CPc#$ckrqLX6lNjUW2rg}W>^U>;0`jdx*O*KJ0$5dlsu6Og z@PI9B*b$v-yk=XU5g|V^azz{xIZ0(<=ZDO-9vjG@66ztEyyMHrc3e~U8+vqEbyVUb z*DAz*jZMSX<4_HfN|(;a74QN2z&9_zhf>Cst_a>>K~ZW8S)~p!Gbhca$H<$JZDS8k zmq3RrUu%6zz}8cnp7TgHYY(v@he+2n-x$%F zw#y@y-%7L10Ilf_<0^F9lC6xUp>nR24##9MmnlZ3gx;wJdvJ})u;v?%1!$3ZTZdil zykiO%VdR_)MAV-AwkGGD*dlH)qSKg^^`7wzZN!#dQt}5$X^I*L%Xr0qfFg>l_%0jl z!yT3Ag4!6H*`_cF?M9IsCpXz}MeHE*oi9m4-%}35TLm*huXxvELF8L~7JJOp%A3Jj z_K;Kj&2~6HadPD|J!uQ{Y;}6Xbw_~&Tf8aQrjX2{Yanu`j16-_^4gg>;3h7ZP>9;{gYUvL!5E*w1FKxVs`@jduHWx@1=MEJYUR%o@~ zm%v02`(G-1>t(sfe;`jn(5Fsv9WcVQ<6w3jt^?=22mi+Z;=NFRD?WyVG0SR+>B^^I zht{}H5X|gyIt+5}x9ENxOK!2OuShlIt+zw(?)?V{Voexg@{wdst^sA%nz`fOW*iu-^Od0{KKy zm}0D=OTj0U0ez~B`{Iped{Xfn-ZK?o7d_kkZQtS&MTn$-hswelt&A#OE>(^QX^5!0 zf$i4n7todJ*@hF(QI~2#^$Tg6WWd(SQt&SXcBRU>DYeC&5a1*Abnb#=qXfl5z>lXB z&(q0lA;c%DJcCHF-ZT3gxh{?Zv{gO!UIy<;dBhlnK47P#Ik`+o@jdoSJ%OSK7!xT+ z++q%vQdZ`FwA%WiQRTrQ&~j&e>>TBRJ6nv(QNHxR9C_p8Na+SPsw2_E$i4GUC1t2N z)R{S}H;rbFn4q@;PFn91p(8P*$6`oN#E_nfAw82qx?G4MU5X*CWRRAuTnwq4I<3W! zuEdZYi6K1}LwX{H^i&M#nG{kfy1x{o`%5vpUy0EI{YsNX1tbNv7~QYMlWqkgFTWC_ v`ztZJzY?SSD>1si5~KSoF}lAJqx&l{y1)8gbbtDjFkgHH;j)HxI(Gm7KPh1k delta 1234 zcmV;@1TFi=3&#rxABzYG=#~CvkqFCwHQn-9_Uty@fopLS@$*Fk#_rDN&r0?v^Cy=7 zOm%hnD^oJi8dFknu$Z_aZZh>V-Fa_Sh`x=$wVA;vNQvHo%UltI;Yv2-&d1c5z!%W> zkd0Pr(Ayz!9WU$X$AnQmUSvwCX*5UNB*u6-f=e48d(MozfPCxFH6~Ml09KWMYJ{9A zJYWkOc0{Kdui4gTM97bfToH#vPEuLe`5|+y#|AQ}gnEc3@AxvZ9oN+Th8|s39hLaV zwFy=rkr}y=Oc_8?mLAl>9+bnxe+RGF~x%pok(XzRL#t za7QJ&pf<*4wkb?PyHVuE$xSv~5j%){=S$Mi_mso%R>6$WE8g{35cyW0#U3-Y@@BA> zJ>(RBvmMS)oLu=#Puc=KTb&+p-BBRH7HBU7aYa13kOd(kl8L~d=I`)Sup(|5&rJ86=AIOss^r@3v2aGW7IGA0B>%e*M!N2jpcrVm{ijN^-%(7Zyy7DR5 zp*8Lk1T(vw4ujnLExI4al3Oh6D^d-4>+R6Hd;bA~SQCbrd?Z&hBtoJ^=Kt53v zrWmW}Qt%07K%Xk(zIdYE2D~+OO+!+8X~H0 zV7s;Y1$3o)w&BEc)TLTb{X*I%8L+jo6#NT;U8!#>2=R$3&mdB)_sl*=u8X4pZB>uGm%)2d9x+Ct57_BwPA(Hte2@K7PoO9Q#ze{y zx0r*al$AMut+sw>RC#a+wA@)AJ4boo&K9F`lrKFnN8b22Qo4bS>PYl3a__uTNf~Mm zb!N`$O{1A3Cg`nzlh*r0=tvCdu^7@5F{GzrNYA8@E*D}*mtsgO8KfmE7egwiPHQow zD>0-;Vn~n0ke-MkJrzTGCWTar?k~mY{!)zYS7LONQ3W3bl^ETx#FKReBa?jv2QRP0 w=>AHK?ytn?{z{DQuf*v7N{sHW#OVG?jP9@g7u}!!B+M6I0UiYTp*nW}0269#l>h($ diff --git a/uk/FAQ/index.html b/uk/FAQ/index.html index d19577650..9dd9a086f 100644 --- a/uk/FAQ/index.html +++ b/uk/FAQ/index.html @@ -1047,7 +1047,7 @@

    How to manage ownership of my t

    Open the team site to which you want add a second owner.

  • -

    Click ‘Manage Users’ under the user menu by clicking on the profile icon in the top-right of Grist.

    +

    Click ‘Manage Team’ under the user menu by clicking on the profile icon in the top-right of Grist.

  • Add the new email address as Owner, and click Confirm.

    @@ -1065,7 +1065,7 @@

    How to manage ownership of my t

    Go to ‘Billing Account’ (also under the user menu) and add the new Owner as a Billing Manager.

  • -

    The new Owner should log in, open the team site, and visit ‘Manage Users’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

    +

    The new Owner should log in, open the team site, and visit ‘Manage Team’ and ‘Billing Account’ pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account.

  • It is not possible to add a second owner to, or transfer ownership of, a personal account.

    diff --git a/uk/index.html b/uk/index.html index 9d291be92..8b6ff9327 100644 --- a/uk/index.html +++ b/uk/index.html @@ -1098,5 +1098,5 @@

    Contact us Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Users\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Users\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"lightweight-crm/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"investment-research/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"afterschool-program/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"creating-doc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"copying-docs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"imports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"exports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"automatic-backups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"document-history/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"workspaces/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"enter-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"page-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"raw-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"search-sort-filter/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"widget-table/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"widget-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-form/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-chart/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-calendar/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-custom/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord

    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"linking-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"custom-layouts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"record-cards/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"summary-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"on-demand-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"col-types/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"col-refs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"conditional-formatting/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"timestamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"authorship/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"col-transform/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"formulas/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"references-lookups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"dates/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"formula-timer/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"python/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"formula-cheat-sheet/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"ai-assistant/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"teams/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"team-sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"access-rules/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"rest-api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.
    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"integrators/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"embedding/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"webhooks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . description: How to configure webhooks for some external integrations # Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#description-how-to-configure-webhooks-for-some-external-integrations","text":"","title":"description: How to configure webhooks for some external integrations"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"code/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/modules/grist_plugin_api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"self-managed/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"install/saml/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"install/oidc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/forwarded-headers/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/cloud-storage/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/grist-connect/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/aws-marketplace/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"telemetry/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"telemetry-limited/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry-full/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"newsletters/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"newsletters/2024-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2021-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"examples/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"examples/2020-06-credit-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-06-book-club/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-07-email-compose/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"keyboard-shortcuts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"limits/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"data-security/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"browser-support/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"glossary/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . title: Welcome to Grist # Welcome to Grist! # Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos. How-To Tutorials # Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity. Intro Videos # Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc Popular shortcuts # Frequently Asked Questions Function reference Keyboard shortcuts Contact us # If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Home"},{"location":"#title-welcome-to-grist","text":"","title":"title: Welcome to Grist"},{"location":"#welcome-to-grist","text":"Grist is a software product to organize, analyze, and share data. Grist Overview Demo Grist combines the best of spreadsheets and databases. Grist lets you work with simple grids and lists, and is at its best when data gets more complex. To sign up and start using Grist, visit https://docs.getgrist.com . To learn Grist, we recommend starting with our How-To tutorials, or our Intro videos.","title":""},{"location":"#how-to-tutorials","text":"Create a custom CRM . Using the \u201cLightweight CRM\u201d example, learn to link data, and create high-productivity layouts. Analyze and visualize data . Using the \u201cInvestment Research\u201d example, learn to create summary tables and charts, and link charts dynamically. Managing business data . Using the \u201cAfterschool Program\u201d example, learn to model business data, use formulas, and manage complexity.","title":"How-To Tutorials"},{"location":"#intro-videos","text":"Creating a doc Pages & widgets Columns & types Reference columns Linking widgets Sharing a doc","title":"Intro Videos"},{"location":"#popular-shortcuts","text":"Frequently Asked Questions Function reference Keyboard shortcuts","title":"Popular shortcuts"},{"location":"#contact-us","text":"If you have questions not answered here, problem reports, or other feedback, please contact us! Email: support@getgrist.com","title":"Contact us"},{"location":"FAQ/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Frequently Asked Questions # Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app? Accounts # Can I add multiple teams to the same Grist login account? # Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams. Can I add multiple login accounts to Grist? # Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I update my profile settings? # Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate. How can I change the email address I use for Grist? # It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. How do I delete my account? # You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here . Plans # Why do I have multiple sites? # All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access. How to manage ownership of my team site? # Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other. Can I edit my team\u2019s name and subdomain? # You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 . Documents and data # Can I move documents between sites? # Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents . How many rows can I have? # As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits . Does Grist accept non-English characters? # Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas. How do I sum the total of a column? # To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables. Sharing # What\u2019s the difference between a team member and a guest? # Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price. Can I only share Grist documents with my team? # There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how . Grist and your website/app # Can I embed Grist into my website? # Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist . Can I use Grist as the backend of my web app? # Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"FAQ"},{"location":"FAQ/#frequently-asked-questions","text":"Frequently Asked Questions Accounts Can I add multiple teams to the same Grist login account? Can I add multiple login accounts to Grist? How do I update my profile settings? How can I change the email address I use for Grist? How do I delete my account? Plans Why do I have multiple sites? How to manage ownership of my team site? Can I edit my team\u2019s name and subdomain? Documents and data Can I move documents between sites? How many rows can I have? Does Grist accept non-English characters? How do I sum the total of a column? Sharing What\u2019s the difference between a team member and a guest? Can I only share Grist documents with my team? Grist and your website/app Can I embed Grist into my website? Can I use Grist as the backend of my web app?","title":"Frequently Asked Questions"},{"location":"FAQ/#accounts","text":"","title":"Accounts"},{"location":"FAQ/#can-i-add-multiple-teams-to-the-same-grist-login-account","text":"Yes! You may create multiple team sites. Each team site may be on the free or a paid plan. Each team site on a paid plan is associated with its own subscription, and is billed separately. New team site. If you\u2019re looking to create a new team site, navigate to your personal site at docs.getgrist.com , then click on top-left site name (@your-name) to open a list of sites. Click on the \u2018+ Create new team site\u2019. In the pop-up, select the plan to use. Adding account to team site. You may own or be a member of multiple teams sites. If you have multiple Grist login accounts, you may also add your second account as a team member. While in the team site you own, open the user menu and click on \u2018Manage Users\u2019. Did you know? A single team site can work well for an organization with multiple teams (or subteams). You can use workspaces within a team site, and manage access to them to create separate areas for different teams.","title":"Can I add multiple teams to the same Grist login account?"},{"location":"FAQ/#can-i-add-multiple-login-accounts-to-grist","text":"Yes! To add multiple accounts to Grist, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"Can I add multiple login accounts to Grist?"},{"location":"FAQ/#how-do-i-update-my-profile-settings","text":"Open the user menu by clicking on the profile icon in the top-right of Grist, then select \u2018Profile Settings\u2019. From here, you can manage the name associated with your account, update Grist\u2019s theme to light or dark mode, set a language and create and manage an API key. To learn more about our API, check out Grist API . Would you like to help translate Grist? We use Weblate to manage translations and welcome volunteer translators! If you\u2019d like to translate Grist into a new language (thank you!) let us know which language in this Community thread , and we\u2019ll add the language on Weblate.","title":"How do I update my profile settings?"},{"location":"FAQ/#how-can-i-change-the-email-address-i-use-for-grist","text":"It is not possible to change the email associated with your Grist account. However, it it possible to transfer ownership of documents and team sites between two Grist email accounts that you own. This would effectively change your Grist email. Learn how . It is possible to manage multiple accounts in Grist. To add another account, open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu.","title":"How can I change the email address I use for Grist?"},{"location":"FAQ/#how-do-i-delete-my-account","text":"You can delete your account in \u2018Profile Settings\u2019 by selecting \u2018Delete Account\u2019 under \u2018Privacy & Data\u2019. Please note that this action is permanent. Need to delete a team site? Learn more here .","title":"How do I delete my account?"},{"location":"FAQ/#plans","text":"","title":"Plans"},{"location":"FAQ/#why-do-i-have-multiple-sites","text":"All Grist users have access to a free personal site. The personal site is always named beginning with \u2018@\u2019 and is always located at docs.getgrist.com . Each document in this site may be shared with up to two guests for free. Learn more about free plans on our pricing page . Documents shared with you from other personal accounts will be shown in your personal site in workspaces named with \u2018@Name\u2019 to indicate the owner of that document. You may navigate between your personal site and team sites by clicking in the top-left corner to open a drop-down menu of sites to which you have access.","title":"Why do I have multiple sites?"},{"location":"FAQ/#how-to-manage-ownership-of-my-team-site","text":"Add a second owner Open the team site to which you want add a second owner. Click \u2018Manage Team\u2019 under the user menu by clicking on the profile icon in the top-right of Grist. Add the new email address as Owner, and click Confirm. You may also wish to go to \u2018Billing Account\u2019 (also under the user menu) and add the new owner as a Billing Manager. Transfer ownership Follow steps 1-3 above to add a second owner. Go to \u2018Billing Account\u2019 (also under the user menu) and add the new Owner as a Billing Manager . The new Owner should log in, open the team site, and visit \u2018Manage Team\u2019 and \u2018Billing Account\u2019 pages again to remove the original owner. This will essentially transfer the ownership of the team site to the new account. It is not possible to add a second owner to, or transfer ownership of, a personal account . Did you know? If you\u2019re transferring team site ownership between two Grist email accounts that you own, you can more easily transfer ownership by signing in with multiple accounts. To sign in with another account, open the user menu and click on \u2018Add Account\u2019. You can now easily switch between all your accounts, and all your teams, from the user menu. Follow the steps above to transfer ownership from one account to the other.","title":"How to manage ownership of my team site?"},{"location":"FAQ/#can-i-edit-my-teams-name-and-subdomain","text":"You may edit your site name and subdomain from the billing page. Open the user menu by clicking on the profile icon in the top-right of Grist, then click on \u2018Billing Account\u2019 .","title":"Can I edit my team\u2019s name and subdomain?"},{"location":"FAQ/#documents-and-data","text":"","title":"Documents and data"},{"location":"FAQ/#can-i-move-documents-between-sites","text":"Yes! Follow these steps to move documents between sites. Open the document you wish to move and click on the share icon ( ), then click \u2018Duplicate Document\u2019 in the menu. Select the site (organization) to which you want to transfer the document. This will create a copy, so you\u2019ll still have the original document in your original site. You can always delete it. Learn more about copying documents .","title":"Can I move documents between sites?"},{"location":"FAQ/#how-many-rows-can-i-have","text":"As a rule of thumb, Grist works best for documents under 100,000 rows. The actual limit depends also on the number of tables, columns, and the average size of data in each cell. One way to estimate it is to measure the size of the data when it is in CSV format: the limit is around 20MB in this format. For example, a document with 200,000 rows and 12 numeric columns would reach that. Attachments are counted separately. Attachments plus data in a single document are limited to 1GB. Learn more about limits .","title":"How many rows can I have?"},{"location":"FAQ/#does-grist-accept-non-english-characters","text":"Non-English characters are supported in column labels, but not column ids, which are the column\u2019s Python name used in formulas. When importing a file into Grist, non-English characters do import as values in cells, but do not import into column labels (aka headers). The column labels are approximated with English characters. You may rename the column labels to include non-English characters after import. To edit column labels and ids separately, open the creator panel and select the column menu. Click the link icon that joins label to id to enable column id editing. While non-English characters are not supported, it is possible to edit the ids into something more clear and friendly to use in formulas.","title":"Does Grist accept non-English characters?"},{"location":"FAQ/#how-do-i-sum-the-total-of-a-column","text":"To summarize multiple records, you need summary tables . The major difference between Grist and spreadsheets like Excel or Google Sheets is that Grist is a database, so each row in a grid represents a record (e.g. a person, a bank transaction, etc.) Since a sum across multiple records isn\u2019t the same kind of record, Grist offers summarizing data as a separate widget, which can be modified to calculate even more powerful summary data. For example, suppose you have a table of webstore orders called Orders and you want to sum sales revenue from all orders. At the top of the left-side menu, click \u2018Add New\u2019 button and select \u2018Add Page\u2019 or \u2018Add Widget to Page\u2019. In the widget picker, select the Orders table then click the summation icon ( ) next to it. Add the page or widget to your document. Done! You\u2019ve now created a summary table that by default counts all the records in the Orders table, and sums all numeric and integer columns, such as the amount sold in each order. Summary tables can do more than a sum of a tables\u2019 columns. With just a few clicks you can summarize orders by month to calculate the number of orders and total sales revenue in each month for which you have data. Take it a step further and suppose you have multiple webstores. With Grist, you can record all sales orders across all stores and months in the same table. Then, you can summarize by month and webstore. When you add more months (or stores!) to the orders table, the summary table will automatically update, which is another key difference with spreadsheets. In Excel, you would need to expand your formula\u2019s range, or add more sheets and modify formulas everytime you added more sales orders, months, or stores. In Grist, you create the summary table once, and let Grist do the future work for you. In addition to our summary tables article, we have a video tutorial and follow-along guide that provides an example in building a dashboard with summary tables.","title":"How do I sum the total of a column?"},{"location":"FAQ/#sharing","text":"","title":"Sharing"},{"location":"FAQ/#whats-the-difference-between-a-team-member-and-a-guest","text":"Team members all have access to a team site , which is located at a url with a custom subdomain, such as your-team.getgrist.com . On paid plans, the number of team members determines the price. By default, documents within a team site may be accessed by all team members. This default can be modified to only share certain documents or certain workspaces with specific team members. Guests, on the other hand, are invited to particular documents, but are not added to your team. All documents in Grist, including those on personal sites, are allowed up to 2 free guests, and do not affect the plan price.","title":"What’s the difference between a team member and a guest?"},{"location":"FAQ/#can-i-only-share-grist-documents-with-my-team","text":"There are many ways to share Grist data with non-team members. Guests. Each document may be shared with 2 guests (non-team members) at no additional cost. Link sharing. In share settings, there is an option to turn on public access . The public access role can be set to viewer or editor. Anyone with a link can view (or edit) your data. Those views would not count towards your plan\u2019s user count. The document is visible to anyone with the link, however, so use caution when working with sensitive data. Restricted view-only link sharing. With view-only link sharing, there is a way to further restrict what people can see by using Grist\u2019s access rules to set specific URL parameters called link keys that determine which tables, columns, or rows are shown when a specific link is shared. View-only embed. Grist pages can be embedded into websites in an iframe. Learn how .","title":"Can I only share Grist documents with my team?"},{"location":"FAQ/#grist-and-your-websiteapp","text":"","title":"Grist and your website/app"},{"location":"FAQ/#can-i-embed-grist-into-my-website","text":"Yes, it is possible to embed Grist pages as view-only data in a website in an iframe. Replace the url written in the code above with the URL of the Grist page you wish to embed, follow by ?embed=true at the end of the url. Learn more about embedding Grist .","title":"Can I embed Grist into my website?"},{"location":"FAQ/#can-i-use-grist-as-the-backend-of-my-web-app","text":"Using Grist as a backend solution directly to a website or web app is not yet supported. We don\u2019t yet have an authentication method for this use case. The API key method isn\u2019t generally appropriate for web use, because the key would end up accessible to any viewer of the webpage, which is usually undesirable. We are interested in supporting this in the future and welcome ideas and feedback in our community forum .","title":"Can I use Grist as the backend of my web app?"},{"location":"lightweight-crm/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to create a custom CRM # Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts Exploring the example # Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example. Creating your own # The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact. Adding another table # For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d. Linking data records # Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly. Setting other types # In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete. Linking tables visually # The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon. Customizing layout # Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other. Customizing fields # At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application. To-Do Tasks for Contacts # The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it. > Setting up To-Do tasks # To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it. Sorting tables # We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon. Other features # Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Create your own CRM"},{"location":"lightweight-crm/#how-to-create-a-custom-crm","text":"Grist is as easy to use as a spreadsheet, but gives you new powers when data doesn\u2019t fit in a simple grid. A good example is keeping track of contacts and our conversations with them. For a business, this could be customers, sales leads, or job candidates. For an individual, it could be companies they have applied to in a job search. In this tutorial, we\u2019ll explain the \u201cLightweight CRM\u201d example, which you can use as a template for your own contacts, and then show how to build it from scratch. You\u2019ll learn how to: Add tables Link data Set column types Create custom layouts","title":"Intro"},{"location":"lightweight-crm/#exploring-the-example","text":"Open the document Lightweight CRM , found in Examples & Templates in your Grist home page . You\u2019ll see the \u201cContacts\u201d page, listing contacts on the left of the screen. Click on any contact to select it. The two sections on the right show the details and the history of interactions with the selected contact. How does this compare to a spreadsheet? These screenshots show the Lightweight CRM example on the left, and a regular spreadsheet with the same data on the right. Previous Next Previous Next The difficulty is in the history of notes for a contact. In a two-dimensional grid, you have few options for where to include multiple notes. If you include them as multiple columns, it quickly makes the spreadsheet unwieldy and difficult to navigate. Grist feels more like an application, but it\u2019s still as versatile as a spreadsheet. The \u201cLightweight CRM\u201d example can be used immediately as-is (with the sample data), or as a template (just the structure without the data). Here are a few more points on using it as a CRM: To add a new contact, click the blank row at the bottom of the contacts list, then fill in the blank \u201cCONTACTS Card\u201d section that shows on the right. To add a new conversation, select a contact, then click the blank line at the end of the Interactions table. You can enter today\u2019s date using the shortcut \u2318 + ; (semicolon) (Mac) or Ctrl + ; (semicolon) (Windows). Then select the type of interaction using auto-complete, and type in your notes. You can add To-Do items for a contact: in the Interactions list, select \u201cTo-Do\u201d in the \u201cType\u201d column as a special type of interaction. Think of the associated date as the due date for this task. The Contacts table shows the list of coming up To-Do items, sorted by their due date. If you use Gmail, the handy \u201cGmail search\u201d link in the \u201cCONTACTS Card\u201d section will open a browser window with the Gmail search results for this contact\u2019s email address. You can use this example as a template for your own contacts. With the \u201cLightweight CRM\u201d example open, click the \u201cSave Copy\u201d button in in the top bar, then mark the \u201cAs Template\u201d checkbox. You\u2019ll get an empty document with the same layout, and can start filling it in with your own data. If you aren\u2019t signed in, you will need to sign in to make a copy of the example.","title":"Exploring the example"},{"location":"lightweight-crm/#creating-your-own","text":"The rest of this tutorial will show you how to create such a document on your own. It\u2019s a great exercise that will teach you some of the key features of Grist. To start, we\u2019ll import a file with sample contacts from the Grist home page. First, save this file to your desktop: lightweight-crm-contacts.csv . Then click the \u201cAdd New\u201d button on the top left of your Grist home page, click \u201cImport document\u201d, and select the file on your desktop. You\u2019ll see a table of contacts with sample data. Note that in Grist, columns have names. Rename this table to \u201cContacts\u201d by clicking its name in the top bar, and typing the new name. That\u2019s all you need for a simple table of contacts. You can add rows here, or add new columns to associate more data with each contact.","title":"Creating your own"},{"location":"lightweight-crm/#adding-another-table","text":"For our next step, we want to be able to select a contact, and see the list of conversations with that contact. These conversations should be a new table of data. The cue is that it has a different number of rows from the table of contacts. Create the new table using the green \u201cAdd New\u201d button on the top left of your screen, and click \u201cAdd Empty Table\u201d in the menu. This table will represent interactions with our contacts, so let\u2019s rename it \u201cInteractions\u201d by clicking its default name (\u201cTable1\u201d) on top of the screen, as before. It\u2019s a good idea to give meaningful names to columns. In this case, for each interaction, we need to know which Contact it refers to, the date, type, and conversation notes. To rename a column, click its header to select the column, and click the header again to edit its name. You can hit the Tab key to continue to renaming the next column. Finally, hit the \u201c+\u201d button to the right of the last column to create one more column, and name it \u201cNotes\u201d.","title":"Adding another table"},{"location":"lightweight-crm/#linking-data-records","text":"Every record in this table will belong to a particular contact. You set it up by turning the \u201cContact\u201d column into a reference to the table \u201cContacts\u201d. Using the triangle in the header of the column \u201cContact\u201d, open the menu and select \u201cColumn Options\u201d. In the right panel, use the \u201cColumn Type\u201d dropdown to select \u201cReference\u201d, then under \u201cData from table\u201d, select \u201cContacts\u201d. Each cell in this column will hold a pointer to a row in the \u201cContacts\u201d table 1 . While it refers to an entire row, it\u2019s useful to see some particular identifier of that row, so under \u201cShow column\u201d, select \u201cCompany\u201d. You\u2019ll see this in action shortly.","title":"Linking data records"},{"location":"lightweight-crm/#setting-other-types","text":"In Grist, every column has a type. Often, the default of Text or Numeric is correct. For our \u201cDate\u201d column, a better type is Date. Click any cell in the \u201cDate\u201d column, and in the right panel, click into the \u201cColumn Type\u201d dropdown and select \u201cDate\u201d. If you\u2019d like, you can also choose a different date format right below the type. Now, if you click on a cell in the \u201cDate\u201d column and hit Enter, you have a convenient date picker. Another useful column type for us is \u201cChoice\u201d. Our interactions will be either \u201cPhone\u201d, \u201cEmail\u201d, or \u201cIn-person\u201d, and it\u2019s useful to list these options. Click into the \u201cType\u201d column, and in the right panel, set \u201cColumn Type\u201d to \u201cChoice\u201d. You\u2019ll see \u201cChoice Values\u201d textbox below. Click it, and enter your choices there, one per line: \u201cPhone\u201d, \u201cEmail\u201d, \u201cIn-person\u201d. Now, if you click on a cell in the \u201cType\u201d column and hit Enter, you can now choose from among the choices you set, or start typing and use auto-complete.","title":"Setting other types"},{"location":"lightweight-crm/#linking-tables-visually","text":"The next step is to link the two tables visually. Open the \u201cContacts\u201d page, click the \u201cAdd New\u201d button on top of the left panel, then \u201cAdd Widget to Page\u201d. Select widget \u201cTable\u201d and data \u201cInteractions\u201d. In the \u201cSelect By\u201d dropdown at the bottom of the dialog, select \u201cCONTACTS\u201d. This means that choosing a contact will display only the interactions with that contact. Click \u201cAdd to Page\u201d to finish. Next, let\u2019s select a contact in the table on the left (let\u2019s use \u201cDouglas LLC\u201d in the fourth row) and add some notes for it. Type in a date (hint: the shortcut \u2318 + ; (semicolon) on Mac or Ctrl + ; (semicolon) on Windows inserts today\u2019s date), select a type, and enter a note. As soon as that row is created, the \u201cContact\u201d column is automatically filled with \u201cDouglas LLC\u201d, thanks to the sections being linked. The note we added is shown only when \u201cDouglas LLC\u201d is selected. We can add more notes for \u201cDouglas LLC\u201d, or add notes for any of the other contacts. We can now hide the \u201cContact\u201d column in the \u201cInteractions\u201d table: using the menu in the column\u2019s header, select \u201cHide Column\u201d. Because the tables are linked, we already see who the notes are for. For longer notes to be convenient, resize the \u201cNotes\u201d column by dragging the right edge of its header. To wrap long notes, open the Column Options, and click the line-wrapping icon.","title":"Linking tables visually"},{"location":"lightweight-crm/#customizing-layout","text":"Once you have multiple tables on one screen, the layout of the screen may become an issue. Having many columns in the Contacts table may no longer be convenient. It\u2019s better to lay it out like a custom application: select a contact from a list on the left and see that contact\u2019s details and interactions. This can be done by using \u201cAdd Widget to Page\u201d again. This time, we\u2019ll select the widget \u201cCard\u201d for the table \u201cContacts\u201d, and for \u201cSelect By\u201d will again use \u201cCONTACTS\u201d. You can move the resulting sections around to create a convenient layout. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. You can also resize sections by moving the mouse between them to find a dotted line. Drag this line to resize. Note how the same personal data is now shown in two places on the screen. These are not copies of data, but different presentations of the same data. Changing the data in one place will change it in the other.","title":"Customizing layout"},{"location":"lightweight-crm/#customizing-fields","text":"At this point, we may do some cleanup: hide unneeded columns in the main \u201cContacts\u201d table and rearrange fields in the Card widget. A quick way to hide columns is using the right panel. Using the three-dot menu on the top right of the \u201cContacts\u201d table, select \u201cWidget options\u201d. In the panel that opens, find a list of \u201cVisible columns\u201d. Move the mouse over each column to reveal the \u201ceye\u201d icon. Click it to hide all columns except \u201cCompany\u201d. To customize the Card widget, click it. The right panel will show the relevant options. You can select a different Theme, e.g. \u201cCompact\u201d. To rearrange fields, click \u201cEdit Layout\u201d in the right panel. You can now drag-and-drop fields in the card, resize them, or remove them. Click \u201cSave\u201d once you are done. In a few short steps, we have gone from a clunky, unwieldy spreadsheet to a concise, elegant record of your interactions in a simple, effective custom application.","title":"Customizing fields"},{"location":"lightweight-crm/#to-do-tasks-for-contacts","text":"The \u201cLightweight CRM\u201d example has another trick up its sleeve. The \u201cType\u201d column in the interactions table has an extra choice, \u201cTo-Do\u201d. After you talk to a contact, you can add an extra note about what you need to do for the next conversation, and the date when it\u2019s due. The \u201cContacts\u201d table makes these To-Do items visible, and sortable by due date. This way you can see at a glance what\u2019s coming up next. If you are interested in the details of setting it up, expand the section below. For your first introduction to Grist, you are welcome to skip it.","title":"To-Do Tasks for Contacts"},{"location":"lightweight-crm/#setting-up-to-do-tasks","text":"To set up To-Do items as in the example, select Column Options for the \u201cType\u201d column in the Interactions table, and add another choice (\u201cTo-Do\u201d) to the list of choices: Let\u2019s pick our contact \u201cDouglas LLC\u201d and add a To-Do item: In the \u201cContacts\u201d table, add two new columns: Rename them to \u201cDue\u201d and \u201cTo-Do Items\u201d. Both columns are calculated using formulas. Grist has great support for formulas, allowing full Python syntax and many Excel functions. In Grist, a formula always applies to the entire column of data. To enter a formula, click on a cell in the \u201cDue\u201d column, and hit \u201c=\u201d key: In this formula, we want to look up all Interactions for the current Contact whose Type is \u201cTo-Do\u201d, then select the one with the earliest Date. Using Python syntax, the formula is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Paste it in, or type in. When typing in multi-line formulas, use Shift+Enter to add new lines, and Enter to save. It\u2019s also a good time to change the column type to \u201cDate\u201d. Open Column Options, and select \u201cDate\u201d for the type. You can choose the Date Format directly below the type. For the \u201cTo-Do Items\u201d, enter a formula similarly. In case of multiple To-Do items, this formula will concatenate them, separated by line breaks. Click into the \u201cTo-Do Items\u201d column, hit \u201c=\u201d to start typing the formula, and enter items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return \"\\n\".join(items.Notes) Now the To-Do item we entered earlier is visible in the main Contacts table. Note that the values in these columns are read-only, since they are calculated. To change the due date, find the To-Do item in the Interactions table where you created it.","title":""},{"location":"lightweight-crm/#sorting-tables","text":"We\u2019ll want contacts with To-Do items to show up first, in order of the Due date. Click the triangle in the header of the \u201cDue\u201d column, and select \u201cSort A-Z\u201d. By default, sorting settings are not saved. The highlighted green button in the top right of the \u201cContacts\u201d table reminds us of that. To keep this sort order when you reopen the document, save it by clicking that green button and selecting \u201cSave\u201d. You can also save by clicking the green check mark, to the right of the filter icon.","title":""},{"location":"lightweight-crm/#other-features","text":"Grist has more great features, some of which are used in the \u201cLightweight CRM\u201d example document. To read more about those, follow the links to their documentation. Any text column may be shown as a hyperlink . Lightweight CRM example uses it twice: for the \u201cwebsite\u201d field, and for a formula-constructed hyperlink to a Gmail search page for the given contact\u2019s email. The latter is handy if you use Gmail. Grist supports attachments . In the example, there is an \u201cAttachments\u201d field for each contact that may be used to store an image of a business card, for example. In an actual business, you\u2019ll need more. Specialized CRM products have tons of features. Their problem is complexity: trying to satisfy the needs of many different clients makes for a complicated product to use. The beauty of Grist is that you can start simple and add only the level of complexity you need, and nothing more. Other tutorials show how to model more complex data, analyze and chart data, and more. In the database world, this kind of reference or pointer is known as a \u201cforeign key\u201d. \u21a9","title":"Other features"},{"location":"investment-research/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to analyze and visualize data # Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data. Exploring the example # Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful. How can I make this? # With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step. Get the data # Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d. Make it relational # The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record. Summarize # The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers. Chart, graph, plot # You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d. Dynamic charts # If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one. Next steps # If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Analyze and visualize"},{"location":"investment-research/#how-to-analyze-and-visualize-data","text":"Grist offers several powerful ways to analyze and visualize data. In this tutorial, you\u2019ll learn how to: Create summary tables Create and configure charts Link charts dynamically To explain these features, we\u2019ll use the sample document \u201cInvestment Research\u201d 1 which includes companies and investments in them up to 2013. Let\u2019s take a look at the sample document and then we\u2019ll talk about how to build it so that you can apply these tools to your own data.","title":""},{"location":"investment-research/#exploring-the-example","text":"Open the document \u201c Investment Research \u201d, found in Examples & Templates in your Grist home page. The first thing you\u2019ll see is \u201cOverview\u201d. This page contains two charts next to two tables. The top left has a pie chart showing the distribution of investments by category. The table next to it has the same data in tabular form. Below the pie chart is a bar graph showing the total investments raised by year. It is also accompanied by the same data in the table next to it in tabular form. All these charts and tables are examples of \u201csummary tables\u201d, which we\u2019ll describe below. The next page, \u201cBreakdowns\u201d, also contains two tables and two charts, but these are linked dynamically and offer much more detailed insight into the data. On the top left is a table showing the total funding by year (the same table as we saw on the previous page). This table serves as a driver for the chart next to it. When you click on a year in the table, the pie chart updates to show the distribution of investments in that year. Similarly, the bottom table shows investments by category. When you click on any category, the line chart next to it updates to show the history of funding in that category over the years. Note how powerful this is, and how much insight you can gain from it. For instance, you can see that Advertising category has been getting a lot of investment in NY since 2007, but was overtaken by E-commerce in 2013, while the Fashion category had a major spike in 2011. On the next page, \u201cCompany Details\u201d, we get to see the granular data of this dataset. Here, we see a list of companies and the categories they fall into. Each company shown has a link pointing to its listing on the Crunchbase website. Selecting a company shows a card with its details, as well as a list of all the investments it has received. This is where we begin to see the power of Grist. The original dataset is a flat spreadsheet of companies, and an even bigger spreadsheet of investments. By displaying the data graphically, the data comes alive, making it powerful and useful.","title":"Exploring the example"},{"location":"investment-research/#how-can-i-make-this","text":"With Grist, presenting your own data in graphic form is a few easy steps away. Let\u2019s begin with the first step.","title":""},{"location":"investment-research/#get-the-data","text":"Let\u2019s import the raw data. We\u2019ll import two CSV files, where each will become its own table. To follow along, save the files from crunchbase_companies_ny.csv and crunchbase_investments_ny.csv to your computer first. Then, create a Grist document by importing the first file from the home page. Next, import the second table using the \u201cAdd New\u201d button and the \u201cImport from file\u201d option. In the import dialog box, finish by clicking \u201cImport\u201d on the bottom left. The tables you\u2019ve imported will be named \u201ccrunchbase_companies_ny\u201d and \u201ccrunchbase_investments_ny\u201d. Click the name at the top of the table to open the dialogue box and rename each of the tables to \u201cCompanies\u201d and \u201cInvestments\u201d.","title":"Get the data"},{"location":"investment-research/#make-it-relational","text":"The power of Grist comes from giving structure to the data. Take a look at the \u201cInvestments\u201d table. Sort by the first column and you\u2019ll notice how much repetition there is: each row contains the complete company info, which both duplicates the data in the \u201cCompanies\u201d table, and is repeated multiple times when multiple investments apply to the same company. 2 The reality is that each investment applies to a single company. Each investment row only needs to contain a reference to a company, and the data specific to that investment. To make it so, find a column that identifies a company uniquely. In this dataset, the first column, \u201ccompany_permalink\u201d, does it best 3 . Click on the arrow in the column header and click \u201cColumn Options\u201d. Click on the arrow beside \u201cText\u201d under the \u201cColumn Type\u201d in the dialogue box at the right of the screen and select \u201cReference\u201d from the list. Grist will automatically suggest to make it a \u201cReference\u201d to the \u201cCompanies\u201d table, and to show the referenced company\u2019s \u201cpermalink\u201d. Click \u201cApply\u201d to save this conversion. Let\u2019s also rename this column to \u201cCompany\u201d. In Grist, duplicated data is not needed and we recommend removing it. Using the Option-Minus (Mac) or Alt-Minus (Windows) shortcut is a quick way to remove columns. After removing the columns from \u201ccompany_name\u201d to \u201ccompany_city\u201d, here\u2019s what\u2019s left: The data you\u2019ve deleted isn\u2019t lost since it was duplicated \u2013 it\u2019s still available in the \u201cCompanies\u201d table and can be used in an Investment record\u2019s formula as, e.g. $Company.company_xxx . In fact, there\u2019s a handy way to create this kind of formula. Let\u2019s create one that we\u2019ll need later. Click the header of the \u201cCompany\u201d column. In the right panel\u2019s Column tab, you\u2019ll see a section \u2018Add Referenced Columns\u2019. Click \u2018Add Column\u2019 to add the \u201ccategory_code\u201d column. A new column will be added to the table with the formula $Company.category_code . For each investment, it shows the \u201ccategory_code\u201d of the company linked to its investment record.","title":"Make it relational"},{"location":"investment-research/#summarize","text":"The powerful feature you\u2019ve been waiting for is the one that summarizes the data. Summary tables summarize each numeric column in a data table. We want to find the sum for the funding_total_usd column in the Companies table. Check that the column type is set to \u2018Numeric\u2019 and formatted with $ . To utilize this, let\u2019s add a table showing companies grouped by \u201ccategory_code\u201d. In the \u201cAdd New\u201d menu at the top left, select \u201cAdd Page\u201d. In the dialog box, select \u201cTable\u201d and \u201cCompanies\u201d, and then use the summation icon ( ) to select the \u201cGroup By\u201d columns \u2013 i.e. the columns by which to summarize. If you don\u2019t select any columns, you\u2019ll just get a single row of totals. If you summarize by \u201ccategory_code\u201d, you\u2019ll get a row for each distinct value of \u201ccategory_code\u201d. Let\u2019s do that and then click \u201cAdd Page\u201d. This is similar to Excel\u2019s pivot tables. Each row represents the group of records from the source table (\u201cCompanies\u201d) that have a particular value of \u201ccategory_code\u201d. There is a reminder of that in the table\u2019s title (\u201cCOMPANIES [by category_code]\u201d). Such summary tables can (and should!) use formulas. The columns you choose when creating the table are the identifiers of the groups. All other columns are formula columns \u2013 they are calculated. In formulas, the group of source records summarized by one row is available as the value \u201c$group\u201d. For example, you\u2019ll see a column created automatically called \u201ccount\u201d. If you hit \u201cEnter\u201d, you\u2019ll see the formula in it \u2013 len($group) \u2013 that\u2019s just the number of records in that group of records, i.e. the number of companies in that category. For numeric columns in the source table, summary tables automatically get a same-named numeric column containing a sum, with a formula such as SUM($group.funding_total_usd) . A note for Python fans $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, i.e. it\u2019s roughly equivalent to [r.A for r in $group] . Sometimes, adding the values doesn\u2019t make sense. E.g. the sum of \u201cfounded_year\u201d is meaningless. It\u2019s best to delete this and any other column we don\u2019t need, which leaves us with the \u201cfunding_total_usd\u201d column. Since this column contains large numbers, it\u2019s a good time to look at the \u201cNumber Format\u201d section of its configuration, and click , (or perhaps $ ) to format numbers to be more readable. Let\u2019s add a second summary table. Select \u201cAdd New\u201d again to \u201cAdd Widget to Page\u201d. To get a summary by year, select the \u201cInvestments\u201d table under \u201cSelect Data\u201d, and again use its sum symbol (\u2211) to select the column by which to summarize: \u201cfunded_year\u201d and then click \u201cAdd to page\u201d. This produces a second summary table that shows a record for each year, with each representing a group of \u201cInvestments\u201d rows for that year. The most useful column is \u201craised_amount_usd\u201d, adding all investments made in that year. Let\u2019s delete the unneeded columns. You\u2019ll notice pink values in \u201craised_amount_usd\u201d. That\u2019s because Grist guesses the column type to be an integer.The pink sums are instances where the numbers exceed Javascript\u2019s ability to handle large integers. To correct for this, the type of the column should be switched to \u201cNumeric\u201d (which trades off precision for the ability to represent very large and very small numbers). Change the type to \u201cNumeric\u201c under \u201cColumn options\u201d. This is again a good time to pick a friendlier number format for the column, and to make it wider to fit the longer numbers.","title":"Summarize"},{"location":"investment-research/#chart-graph-plot","text":"You can make a chart of any data. To this page, we want to add a graphic version of each summary table. Select the \u201cAdd New\u201d button again, pick \u201cAdd Widget to Page\u201d, select \u201cChart\u201d as the widget, and the same table (Companies) and summary column (category_code) as before. Then click \u201cAdd to Page\u201d. For a chart, you\u2019ll always follow up by customizing it. Open the right panel, and select \u201cChart\u201d tab / \u201cWidget\u201d subtab. For this first chart, under \u201cChart type\u201d, select \u201cPie Chart\u201d. To construct this chart, first select a label, and then select a series to summarize in the pie chart. Since we want the chart to show \u201ccategory_code\u201d as labels, select this series from the \u201cLabel\u201d dropdown. We want to use \u201cfunding_total_usd\u201d as values, so this should be listed at the top of the \u201cseries\u201d list in the configuration panel. As you move your mouse over the items in that list, use the double vertical bars that show up to drag and drop a series at the top of the list. Alternatively, you can hide the other series from the list by clicking the trash icon. Now add a chart showing a trend by year. Add another \u201cWidget to page\u201d, select \u201cChart\u201d under \u201cWidget\u201d, select \u201cInvestments\u201d under \u201cSelect Data\u201d, click summation ( ) to group by \u201cfunded_year\u201d, and click \u201cAdd to Page\u201d. To customize this chart, stick with the chart type \u201cBar Chart\u201d. From the \u201cX-Axis\u201d dropdown, select the column to use for the X (horizontal) axis values. Under \u2018Series\u2019 select a second (and possible additional) column to be values for the Y (vertical) axis. You can rearrange the sections on the screen into a configuration you\u2019d like to see for a dashboard. Move your mouse to the top left of each section until you see a \u201cdrag handle\u201d icon. Use that icon to drag each section into the desired spot relative to other sections. Once you\u2019re finished, rename the page by hovering over the page name then clicking the three-dot icon to open the menu. Select \u201cRename\u201d and rename to \u201cOverview\u201d.","title":"Chart, graph, plot"},{"location":"investment-research/#dynamic-charts","text":"If you\u2019ve read our other tutorials on linking data, this will come naturally. Charts are simply a different way to show data, and they can be linked in the same way as tables. For our example, we\u2019ll add a new page with a summary table: select widget \u201cTable\u201d, data \u201cInvestments\u201d, group by \u201cfunded_year\u201d, click \u201cAdd Page\u201d. Let\u2019s rename this new page \u201cBreakdowns\u201d. Next, add a widget to this page, selecting widget \u201cChart\u201d, data \u201cInvestments\u201d. For \u201cGroup By\u201d, we pick two columns: \u201cCompany_category_code\u201d and \u201cfunded_year\u201d. This is why we added the \u201cCompany_category_code\u201d column earlier. We can only group investment records by the category code if we have this code for each investment. The \u201cSelect By\u201d dropdown at the bottom left of the dialog box lists widgets already on the screen that can control the selection of data in the chart we are adding. In \u201cSelect By\u201d, choose \u201cINVESTMENTS [by funded_year]\u201d, and click \u201cAdd to Page\u201d. Note: If you need to make changes to a widget you already added, such as change its type, \u201cGroup By\u201d, or \u201cSelect By\u201d settings, you can always do so from the \u201cData\u201d subtab in the widget settings, using the \u201cEdit Data Selection\u201d button. We want to be able to select a year, and then show a pie chart for that year that displays the total for each category code. The \u201cSelect By\u201d option we chose ensures that only the selected year\u2019s data is used. All that\u2019s left is to change the chart type to \u201cPie Chart\u201d, and set \u201cLabel\u201d to \u201cCompany_category_code\u201d and \u201cSeries\u201d to \u201craised_amount_usd\u201d. Note: Graphs need more screenspace, so our small screenshots will look better if we close the side panels by clicking on the opener icons ( , ). Let\u2019s also sort the table by \u201cfunded_year\u201d. As far as sorting, the highlighted button above the table reminds you that sort settings aren\u2019t saved automatically. Click the green button and select \u201cSave\u201d to do that. What\u2019s the result? We can click through the years (or use arrow keys), and see the distribution by category change. Note: If clicking through the years does not affect the chart, the chart must not be linked. You can check and correct it by using the \u201cthree dots\u201d menu on top right of the chart, clicking \u201cData selection\u201d, and ensuring that \u201cSelect By\u201d dropdown is showing \u201cINVESTMENTS [by funded_year]\u201d. To complete the example, we will add two more sections to this \u201cBreakdowns\u201d page. One will be a table listing company categories, and linked to it will be a chart showing the amount of investment in that category over the years. To add the table of categories, use \u201cAdd Widget to Page\u201d, and select \u201cTable\u201d widget, \u201cInvestments\u201d data, grouped by \u201cCompany_category_code\u201d. The \u201cfunded_year\u201d column in the resulting table is meaningless, and should be deleted. For the last step, we add another chart. We need to remember to group by both \u201cCompany_category_code\u201d \u201cfunded_year\u201d, and to set a suitable \u201cSelect By\u201d widget for it. Since there are two tables on this page, you have a choice of which one will drive the data in this chart. In this case, pick the widget that we just added: \u201cINVESTMENTS [by Company_category_code]\u201d. As in the previous section, we configure the chart by selecting \u201cChart Type\u201d as \u201cBar Chart\u201d, and in the \u201cX-Axis\u201d dropdown, selecting \u201cfunded_year\u201d and under \u201cSeries\u201d, selecting \u201craised_amount_usd\u201d and hiding the rest. We can now click through the categories, and see the history of investment into each one.","title":"Dynamic charts"},{"location":"investment-research/#next-steps","text":"If you\u2019re unfamiliar with how we created the \u201cCompany Details\u201d page that\u2019s present in the example, visit one of these earlier tutorials to learn how: \u2018How to build a Lightweight CRM\u2019, or \u2018Managing your Business in Grist\u2019. That\u2019s it! Now go analyze some data! Download crunchbase_companies_ny.csv and crunchbase_investments_ny.csv . The sample data includes only the \u201ccompanies\u201d and \u201cinvestments\u201d data, and includes only New York companies to keep it smaller and faster. The dataset comes from Kaggle . \u21a9 Such duplication is commonly seen in spreadsheets. Data in this form is called \u201cdenormalized\u201d. \u21a9 If you don\u2019t have a single identifying column, you can construct one with a formula. \u21a9","title":"Next steps"},{"location":"afterschool-program/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . How to manage business data # Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document. Planning # A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet. Data Modeling # The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor. Classes and Instructors # When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table. Formulas # Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled. References # Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments. Students # Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy. Many-to-Many Relationships # A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students. Class View # One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page. Enrollment View # Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times . Adding Layers # If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family. Example Document # The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Manage business data"},{"location":"afterschool-program/#how-to-manage-business-data","text":"Grist shines when your business data has some complexity. In this example, we\u2019ll look at an organization that runs after-school activities for children. Feel free to refer to the Class Enrollment document, found on our Examples & Templates page. In this exercise we will show how to go about planning, designing, and creating such document.","title":"Intro"},{"location":"afterschool-program/#planning","text":"A little planning ahead will help you get a solution faster. Think about what kinds of data you are working with, and what kinds of workflows you will need. You don\u2019t need to anticipate everything when you start. You can easily add complexity later, as you\u2019ll see below. Our organization runs a number of classes for children. Each class has an instructor and multiple students. Students may enroll in multiple classes, and may return year after year. For each class, we\u2019ll want to see the list of enrolled students, and if there are spots available. When a parent calls, we want a convenient way to enroll a new student, or to change or cancel a student\u2019s enrollment. If this sounds like building an application, that\u2019s exactly what we are doing. As you\u2019ll see, however, it is no harder than building a spreadsheet.","title":"Planning"},{"location":"afterschool-program/#data-modeling","text":"The better you model your business data, the easier your work will be. In a way, you are doing database design as well as building a custom application. Grist just makes it easier. Let\u2019s start with the classes and their instructors. We need a list of classes and instructors. The same instructor can teach different classes, so instructors and classes will live in different tables. Let\u2019s call them \u201cClasses\u201d and \u201cStaff\u201d. Each class has a single instructor, so one of the properties of a class will be that class\u2019s instructor.","title":"Data Modeling"},{"location":"afterschool-program/#classes-and-instructors","text":"When starting from scratch, you\u2019ll create a new empty document (see Creating a document ), rename the initial empty table \u201cTable1\u201d to \u201cClasses\u201d, add the columns you need, and type in some classes. To follow the steps of this tutorial, you can instead import Classes.csv (or simply refer to the \u201cAfterschool Program\u201d example document). For the Staff table, click the \u201cAdd New\u201d button and select \u201cAdd Empty Table\u201d. Rename it to \u201cStaff\u201d, create some columns, and enter some data about instructors. Or import Staff.csv to use sample data and save a few steps. We want the Instructor column in the Classes table to be a reference to the Staff table. Currently, the Staff table doesn\u2019t have any column that uniquely identifies each record. For setting up references, it\u2019s a good idea to add one. In this case, we\u2019ll add a \u201cFull Name\u201d column to the Staff table.","title":"Classes and Instructors"},{"location":"afterschool-program/#formulas","text":"Click on the \u201cStaff\u201d page, and add a \u201cFull Name\u201d column using the column menu or the Alt + = shortcut, and type \u201cFull Name\u201d in the new column\u2019s header. Create a formula by typing in a value into any cell in the new column, starting with the equal sign (\u201c=\u201d). Enter the formula as $Last_Name + \", \" + $First_Name . You may use Excel-like syntax to the same effect: CONCAT($Last_Name, \", \", $First_Name) . In Grist, a formula always applies to every record in the table. Grist supports Python in formulas, and most Excel functions, which have uppercase names. Because formulas apply to every row, you should see the Full Name column automatically filled.","title":"Formulas"},{"location":"afterschool-program/#references","text":"Click on the \u201cClasses\u201d page again, and open \u201cColumn Options\u201d for the Instructor column. We\u2019ll turn it into a reference to Staff . In the right-side panel, set the column type to \u201cReference\u201d (in database terms, this is known as a \u201cforeign key\u201d), and set the referenced table to \u201cStaff\u201d. For the \u201cSHOW COLUMN\u201d dropdown, select \u201cFull Name\u201d, which is the new column we just added. Click the \u201cApply\u201d button to complete this change of the column\u2019s type (if the column is empty, there is no confirmation step). You can now assign an instructor to any class. Click on a cell in the Instructor column. You can hit Enter and choose from among the available instructors in the Staff table, or start typing to use auto-complete. Next, we will continue with students and their enrollments.","title":"References"},{"location":"afterschool-program/#students","text":"Each class has a number of students. So, we\u2019ll need a table of students. Again, add a new empty table, rename it to \u201cStudents\u201d, and fill it with the students\u2019 names, grade levels, etc. Or import Students.csv to use sample data and save a few steps. Let\u2019s add here a \u201cFull Name\u201d formula column, just like in the Staff table. It will come in handy.","title":"Students"},{"location":"afterschool-program/#many-to-many-relationships","text":"A student can take more than one class. Let\u2019s remember also that there are classes in the past and in the future, and keeping this historical data is valuable. So, for each student, there may be multiple classes, and in each class, there are multiple students. A good way to model such a relation is by adding the concept of an \u201cenrollment\u201d and a new Enrollments table. An \u201cenrollment\u201d represents one student being enrolled in one class. It has some useful properties of its own: the enrollment status (Confirmed, Waitlisted, or Cancelled), whether they have paid for the class, and perhaps more. In database design, this is known as a \u201cmany-to-many\u201d relationship. The extra table is known as a \u201cjoin\u201d table. Essentially, it adds a record for each student-class connection, and turns the \u201cmany-to-many\u201d relationship into two \u201cone-to-many\u201d relationships. This relationship: becomes this: So, let\u2019s add a new table, name it \u201cEnrollments\u201d, and add the columns we need. Here too, to follow along, you may import sample data from Enrollments.csv . In Column Options for the column Student , set the Column Type to \u201cReference\u201d, set the referenced table to Students , and select \u201cFull Name\u201d as the column to show. For the column Class , set the Column Type to \u201cReference\u201d, the referenced table to Classes , and select \u201cClass Code\u201d as the column to show. It\u2019s possible to enter enrollment data by adding records to this table and using auto-complete in the Student and Class column. Below, we will set up a much more convenient way to enroll students.","title":"Many-to-Many Relationships"},{"location":"afterschool-program/#class-view","text":"One of our goals in the Planning stage was to see a list of enrolled students for each class. Now that the data tables are in place, we can create a page that does this. Click \u201cAdd New\u201d, then \u201cAdd Page\u201d to open the widget picker. Select to show a table of Classes , and click \u201cAdd Page\u201d. The new page shows a list of classes. Let\u2019s rename this page \u201cClass View\u201d. Next, add the Enrollments table linked to the table of classes. Click \u201cAdd New\u201d again, then \u201cAdd Widget to Page\u201d. In the widget picker, select to show a table of Enrollments . For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d, and click \u201cAdd to Page\u201d. We get two tables side-by-side. Selecting a class shows all enrollments in that class, each of which references a particular student and includes some other enrollment info. We can go a step further to make this view convenient for us. Let\u2019s add a Class card to this view. Click \u201cAdd New\u201d and \u201cAdd Widget to Page\u201d. In the widget picker, select widget \u201cCard\u201d for the Classes data. For \u201cSELECT BY\u201d, choose \u201cCLASSES\u201d again, and click \u201cAdd to Page\u201d. We can similarly add a card for the instructor leading this class. Again, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and select \u201cCard\u201d for the Staff data. For \u201cSELECT BY\u201d, you can now pick \u201cCLASSES \u2022 Instructor\u201d. You can change the look of the new cards. Click the three-dot menu on the top right of the Card you\u2019d like to change, and choose \u201cWidget options\u201d. Find the \u201cTheme\u201d dropdown in the right panel, and select \u201cCompact\u201d. Here is what we now have in Class View page: It\u2019s a good time to clean up this page by hiding columns and fields that are duplicated or distracting. Check out Configuring field lists for an efficient way to select which fields to show, and Custom Layouts for rearranging widgets on the page.","title":"Class View"},{"location":"afterschool-program/#enrollment-view","text":"Our other stated goal was a convenient way to enroll a student, and to see or adjust a given student\u2019s enrollments. Let\u2019s add a page for this. Click \u201cAdd New\u201d > \u201cAdd Page\u201d, and select a table of Students . We\u2019ll rename the new page to \u201cEnrollment View\u201d. When we select a student here, we\u2019d like to see all enrollments for this student. So click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d, and add a table of Enrollments . To link the new widget to the Students widget in the page, set the \u201cSELECT BY\u201d dropdown to \u201cSTUDENTS\u201d, and click \u201cAdd to Page\u201d. Now you can click on a student\u2019s name on the left and see the classes that student is enrolled in on the right. You can enroll the selected student into a class by simply entering a class code in the special blank row at the bottom of the enrollments list. As usual, the Class reference column offers auto-complete. You can hide the \u201cStudent\u201d column in \u201cENROLLMENTS\u201d table, since it will always show the selected student anyway. If you\u2019d like to include more info about the classes in the Enrollments table, select Column Options for the Class column, and click \u201c+ Add Column\u201d in the side panel. Any of the fields associated with a class are available. If you examine the columns you add this way, you\u2019ll see that they are simply formulas of the form $Class.Times .","title":"Enrollment View"},{"location":"afterschool-program/#adding-layers","text":"If you are working with children, you are talking to their parents. You\u2019ll need to have the parents\u2019 names and contact info, which you can add as columns to the students table. You\u2019re likely to find that some parents have multiple children that enroll in classes together. Recognizing the parent-child relationship in your database may seem like a complex step, but it will likely simplify your daily workflow. So, let\u2019s add one more table: Families . We\u2019ll include the parent name and contact info, and link each child to a record here. You can import sample data from Families.csv . Note that we added a Full Name column here as for other tables that list people. In the Students table, we\u2019ll add a column named \u201cFamily\u201d, and make it a Reference to Families . The example data already has families filled in, and conversion to reference looks up the text to set correct references. If you were entering new data, you could link students to families using auto-complete as with other reference columms. Let\u2019s change our \u201cEnrollment View\u201d to list families first. This way, when a parent calls, you can pick the right record and see all of their children and enrollments for each child. In the \u201cEnrollment View\u201d page, click \u201cAdd New\u201d > \u201cAdd Widget to Page\u201d. Select to show a table of Families . Rearrange the widgets to have Families on the left, and Students and Enrollments to the right of it. Now link students to Families: click the three-dot menu on the top right of the STUDENTS table, and choose \u201cData Selection\u201d. In the side panel, you can set \u201cSELECT BY\u201d widget to \u201cFAMILIES\u201d. Finally, you can add Card widgets for details of the selected family, and of the selected student, and rearrange the widgets on the page to create a layout that\u2019s perfect for talking to a parent. You select a parent, see their children, and then select a child to see their enrollments. It\u2019s easy to add an enrollment record for a new class, or to change a record (e.g. mark it as \u201ccancelled\u201d), to add another child, or to add a new family.","title":"Adding Layers"},{"location":"afterschool-program/#example-document","text":"The \u201cAfterschool Program\u201d example includes everything described above and a bit more. In particular, it adds a Count field to Classes to calculate the number of enrolled students, and a Spots Left field to show the number of spots remaining, by comparing Count to Max_Students : The formula for Count is len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) . The formula for Spots Left is max($Max_Students - $Count, 0) or \"Full\" . These make for a more useful Class View page, where it\u2019s now easy to see at a glance which classes have spots remaining.","title":"Example Document"},{"location":"creating-doc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating a document # To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist. Examples and templates # The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens. Importing more data # Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data . Document settings # While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Creating a document"},{"location":"creating-doc/#creating-a-document","text":"To get started with Grist you\u2019ll first need to create a document. Each document can store different kinds of data, so think of it as a collection of related data. You can start from scratch or import existing data. Click the \u201cAdd New\u201d button on the home screen and choose either \u201cCreate empty document\u201d or \u201cImport document\u201d. You can import spreadsheets in Excel format, as well as comma-separated value files or CSVs. If you import a spreadsheet that has multiple tabs, each tab will become a separate table in Grist.","title":"Creating a document"},{"location":"creating-doc/#examples-and-templates","text":"The \u201cExamples & Templates\u201d list on the Grist homepage includes a number of Grist documents that demonstrate Grist features and use cases. These are always accessible via a link on the bottom left of the home page. For each example, there is a corresponding How-To Tutorial in the Grist Help Center, which describes how to use it or how to build such a document from scratch. When you open an example, you can make changes to it. You are editing a copy, and your changes are private to you. You can save your changes by clicking the \u201cSave Copy\u201d button on the top of the page. You can also use any of the examples as a template for a new document, whch will include the structure, layout, and formulas of the example, but none of the sample data. Simply use the \u201cSave Copy\u201d button and check the \u201cAs Template\u201d checkbox in the dialog that opens.","title":"Examples and templates"},{"location":"creating-doc/#importing-more-data","text":"Once you\u2019ve created a document, you can import more data into it by opening the document and selecting \u201cAdd New\u201d and then one of the Import options. You can read more about importing data at Importing more data .","title":"Importing more data"},{"location":"creating-doc/#document-settings","text":"While a document is open, you can access the document\u2019s settings from the user menu or from the Tools menu in the left-hand navigation panel. There you can find the document\u2019s ID for API use, as well as set the default timezone, locale, and currency for the document.","title":"Document settings"},{"location":"sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Sharing # To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations. Roles # There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article. Public access and link sharing # If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d Leaving a Document # Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Sharing a document"},{"location":"sharing/#sharing","text":"To collaborate in Grist, you can invite other users to access a document. For team plans, you can also add users to your team site, or to a workspace (see Team Sharing ). When the document is open, click on the sharing icon ( ) on the top right of the screen. It opens a menu with sharing and export options. Select \u201cManage Users\u201d. This option is also available in the Grist home page, when you click the three-dots icon to the right of a document\u2019s name. If the \u201cManage Users\u201d item is grayed out, it means you don\u2019t have permission to view or manage the sharing settings for this document. The sharing dialog that opens lists the users that have access to the document. To add a user, enter that user\u2019s email address and hit \u201cEnter\u201d or click \u201cInvite new member\u201d in the dropdown. You may select a role for any invited user, and click \u201cConfirm\u201d to save the changes and send any invitations.","title":"Sharing"},{"location":"sharing/#roles","text":"There are three primary roles supported by Grist: Viewer : allows a user to view the document but not make any changes to it. Some operations like sorting and filtering will work without affecting other users of the document. This is the default role when you type in an email address. Editor : allows a user to view or make changes to the document data, structure, or formulas, but not to its sharing settings. Owner : gives a user complete permissions to the document, including to view and change its sharing settings. A document may have one or more owners. If you are able to open the \u201cManage Users\u201d dialog, you have the \u201cowner\u201d role. You may not change your own access, but your access may be reduced or removed by another owner. The option to inherit access does not affect individual plans, and is explained in the Team Sharing article.","title":"Roles"},{"location":"sharing/#public-access-and-link-sharing","text":"If you want to share your document with a wider audience, you can make it publicly accessible. Open the share menu by clicking on the share icon ( ) on top right of the screen and selecting \u201cManage Users\u201d. Toggle the dropdown next to \u201cPublic Access\u201d and select \u201cOn\u201d: Once you click \u201cconfirm\u201d, anyone with the link to your document will be able to view it. They will not be required to have a Grist login. The \u201cCopy Link\u201d button is handy for link sharing. You can copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to edit your document. Simply switch the role for Public Access to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. Should anything go wrong, you may recover previous versions of your document in the snapshots section of Document History. Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. ?style=singlePage can be edited and follows access rules, while ?embed=true is read-only. To learn more about embedding, see Embedding Grist Access rules may be combined with view-only link sharing, i.e. when the Public Access role is set to \u201cViewer.\u201d Read more about Link Keys to learn how to set rules that limit which parts of your data users may see when accessing your document via a particular link. Access Rules are supported with link sharing when Public Access is set to \u201cViewer\u201d or \u201cEditor.\u201d","title":"Public access and link sharing"},{"location":"sharing/#leaving-a-document","text":"Non-owners may look up their access details to a document by clicking on the share icon ( ) on top right of the screen and selecting \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Leaving a Document"},{"location":"copying-docs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Copying Documents # It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document: Trying Out Changes # As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option. Access to Unsaved Copies # When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document. Duplicating Documents # You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document. Copying as a Template # If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data. Copying for Backup Purposes # You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups . Copying Public Examples # When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying documents"},{"location":"copying-docs/#copying-documents","text":"It is sometimes useful to make a copy, or clone, of a Grist document. A few scenarios are described below. In all cases, you would start with the \u201cShare\u201d menu available from the top bar of an open document:","title":"Copying Documents"},{"location":"copying-docs/#trying-out-changes","text":"As your document grows in importance, it becomes riskier to make changes to its structure or logic. That\u2019s a good reason to work out such changes on a copy of the document, without fear of affecting the original. Open the Share menu and click the option \u201cWork on a Copy\u201d. You\u2019ll get an unsaved copy of your document. This copy is special in that it knows which original document it came from (you can see the original document ID included in its URL). You can experiment on this copy, making changes big or small, one or many. For those familiar with software development, this option is similar to a branch or a fork as used in version control systems like Git . When working on a copy, the Share menu has some new options. For example, you can view differences between the document copy and the original, using the \u201cCompare to Original\u201d menu item: New material will be highlighted in green, and old material in red. Once satisfied with your changes, click the option \u201cReplace Original\u201d. Your copy will replace the original document. Grist will warn you if the replacement risks overwriting any recent changes in the original. To discard your changes, simply go back to the original document using the \u201cReturn to Original\u201d option (or the Back button in your browser). Don\u2019t worry about cleaning up your copy. It does not count against any document limits, and will get cleaned up automatically if it has been unused for a while. You can also save your copy under a new name using the \u201cSave Copy\u201d option.","title":"Trying Out Changes"},{"location":"copying-docs/#access-to-unsaved-copies","text":"When you create an experimental copy as described above, it gets a unique link. The copy isn\u2019t listed anywhere, so others will not find it unless you share this link. Anyone with a link to your copy and with access to the original document is allowed to view the copy, but you are the only user allowed to edit it. This means that you can share a link to your copy with others, who can review your changes. It also means that you can try out changes even if you cannot edit the original! You can then share a link to your copy with a collaborator who has edit access, who would be able to review your changes and apply them to the original document.","title":"Access to Unsaved Copies"},{"location":"copying-docs/#duplicating-documents","text":"You can save your document under a new name using the \u201cDuplicate Document\u201d option in the Share menu. Clicking it opens a dialog: Type in the new name. If you have access to one or more team accounts, you may have a choice of a destination team and a destination workspace where to save the copy. Note that on a team site, you would not be able to save the document outside of the team site unless you have owner-level access to the document.","title":"Duplicating Documents"},{"location":"copying-docs/#copying-as-a-template","text":"If you mark the \u201cAs Template\u201d checkbox when saving a copy, you\u2019ll get a document which has all the structure, formulas, and layouts of the original, but none of the data. It makes it easy to use the existing structure for a new set of data.","title":"Copying as a Template"},{"location":"copying-docs/#copying-for-backup-purposes","text":"You can use the \u201cDuplicate Document\u201d option to save the current version of the document as a backup, perhaps appending today\u2019s date to the name of the copy. That said, Grist already makes automatic backups regularly, which may be sufficient for most backup needs. See Automatic Backups .","title":"Copying for Backup Purposes"},{"location":"copying-docs/#copying-public-examples","text":"When you open a public example from the Examples & Templates page , it will open the example in fiddle mode . Fiddle mode is similar to working on a copy, as described above in Trying Out Changes . You may make changes, but they remain private to you. You can save a copy of the example under a new name using the \u201cSave Copy\u201d button or menu option. You can use the \u201cAs Template\u201d checkbox to discard the data of the example, keeping only its structure. This makes it easy to start using it for your own data.","title":"Copying Public Examples"},{"location":"imports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Importing more data # You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option. The Import dialog # When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings. Guessing data structure # In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types. Import from Google Drive # Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import. Import to an existing table # By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document. Updating existing records # Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Importing more data"},{"location":"imports/#importing-more-data","text":"You can import a file to start a new Grist document , or to add data to an existing document. Grist supports imports of Excel, CSV, JSON, tab-separated files and from Google Drive . To start a new Grist document, click the \u201cAdd New\u201d button on the home screen and choose \u201cImport document\u201d, as described in starting a new Grist document . To add to an existing document, open that document, click the \u201cAdd New\u201d button and then \u201cImport from file\u201d. By default, each imported table is added as a new Grist table, but when examining the preview dialog for an import, you have an option to change the destination to an existing Grist table. You can also import any of the same formats from a URL, using the \u201cImport from URL\u201d option.","title":"Importing more data"},{"location":"imports/#the-import-dialog","text":"When you import data into an existing document, Grist opens an import dialog to show you what will be imported. This dialog offers available import options, lets you choose whether to create a new table or add to an existing one, and shows a preview of the data. The \u201cImport options\u201d link on the top right is sometimes useful when importing delimited files. Grist guesses the settings to parse the data (such as the field delimiter), but if it guesses incorrectly, you can adjust the settings.","title":"The Import dialog"},{"location":"imports/#guessing-data-structure","text":"In all cases, when you import a file, Grist makes guesses about the structure of the file. For Excel files, Grist treats each sheet as a separated table. For CSV and other delimited formats, one file becomes one table. For both Excel and delimited files, Grist tries to detect whether the headers are included and which line they occur in. If Grist detects there are no headers, it will name columns as \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc. Grist automatically tries to parse numbers, dates, and boolean fields to detect the most suitable type for each column. It tries to be lossless: e.g. if it marks a column as numeric, any text values in it (such as \u201cN/A\u201d) will remain in the imported table, but will be highlighted due to the type mismatch. You can always rename tables and columns after an import, as well as convert types.","title":"Guessing data structure"},{"location":"imports/#import-from-google-drive","text":"Importing from a Google Drive is as easy as importing from an Excel file or a CSV file. You can either provide an URL of a file stored in the Google Drive or use a Google File Picker to choose a file from your own drive. To use a Picker, click the \u201cAdd New\u201d button and choose \u201cImport from Google Drive\u201d. To import, sign in to your Google Account by clicking the \u201cSign in\u201d button and following the sign-in process. Grist will ask for a permission to read the file you will import from Google Drive. We won\u2019t read any other files on your drive \u2014 just the single file you choose to import. Once the file has been chosen, the rest of the process is the same as importing from an Excel file. In the import dialog you may configure what data to import, and which destination table to add it to. If you have an URL to a file or a spreadsheet stored on your Google Drive or a file that is publicly accessible, you can import it directly using the \u201cImport from URL\u201d option from the \u201cAdd New\u201d menu. If the file is not shared publicly, Grist will ask you for permission to read files from your Google Drive. If you don\u2019t want to allow Grist to read your files, it will open up Google File Picker where you can select the file you want to import.","title":"Import from Google Drive"},{"location":"imports/#import-to-an-existing-table","text":"By default, Grist imports new data as new tables, but the Import dialog allows you to change the destination and import data into an existing table. When importing to an existing table, Grist will attempt to match the columns from your imported file to the columns in the destination table. To manually specify column matching, click the \u2018Source Column\u2019 drop down to open a menu with a list of unused columns from your imported file. Click on a column name to match it to a destination column, or select \u2018Skip\u2019 to skip importing data from that column. You can also specify a formula for each imported column by clicking \u2018Apply Formula\u2019 from the \u2018Source Column\u2019 drop down menu. Formulas can reference one or more imported columns, and the result of evaluating the formula will be shown in the preview after closing the editor. Importing to an existing table is best suited for importing multiple datasets containing similar structure. For instance, you could import a bank statement as a new table, then import more statements from other months into the same table. For developers, the Grist API offers a more powerful way to add data to a Grist document.","title":"Import to an existing table"},{"location":"imports/#updating-existing-records","text":"Suppose the table we are importing to already contains some of the data in our file. We\u2019d like Grist to update these existing records rather than duplicate them. We can tell Grist to update these records by checking the \u201cUpdate existing records\u201d option, and specifying which fields to use for matching incoming data against existing records. An imported row and an existing record that have the same values for all of the selected merge fields are considered the same record. In this case, importing will update the fields of the record with the imported data. Blank values from the imported data will be skipped, leaving the corresponding fields in the original record unchanged. Note: If there are multiple imported rows that have the same values for the selected merge fields, the last row will be used for matching and updating. If there are multiple existing records that have the same values for the selected merge fields, only the first record will be updated if a matching imported row is found. Each time the merge fields are changed, Grist will generate a preview of the updates that will be made to the destination table, and display them in the preview table. Changes are highlighted as follows: New records have all of their fields highlighted in green. Updated records have red and green highlighting for any changed fields: red (with a strikethrough) for existing values from the destination, and green for new values from the imported file. Unchanged records have no highlighting. Field values that exist in the destination table, but are blank in the imported file, are distinguished by a light gray font color.","title":"Updating existing records"},{"location":"exports/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Exporting # Exporting a table # If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table. Exporting a document # If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document . Sending to Google Drive # If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document. Backing up an entire document # Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d. Restoring from backup # A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Exports & backups"},{"location":"exports/#exporting","text":"","title":"Exporting"},{"location":"exports/#exporting-a-table","text":"If you want to export a table to another spreadsheet or database, you can export that table as either an XLSX file or a CSV, a common interchange format for data. To do this, open your document to the desired table or widget. Then click the three dot menu in the top right of the widget. Select either \u201cDownload as CSV\u201d or \u201cDownload as XLSX\u201d. Your browser will then download a file containing a header row naming your columns, excluding any hidden columns or filtered-out rows, followed by all the rows of data visible in the table.","title":"Exporting a table"},{"location":"exports/#exporting-a-document","text":"If you want to export all tables to Excel format, click the sharing icon ( ) on the top right of the screen and select \u201cExport XLSX\u201d. Your browser will then download an Excel file, where each table is a separate sheet containing all rows, without any filters applied. To use this option you need to have full read access for all tables in a document. A note about \u201cExport CSV\u201d in the sharing menu. When on a page with multiple page widgets, \u201cExport CSV\u201d will export only the data in the currently-selected widget. To export all your data, use \u201cExport XLSX\u201d or the \u201cDownload\u201d option which is explained below in backing up an entire document .","title":"Exporting a document"},{"location":"exports/#sending-to-google-drive","text":"If you want to export a document to Google Drive as a Google Sheet file, click the sharing icon ( ) on the top right of the screen and select \u201cSend to Google Drive\u201d. Grist will first ask you to log in to your Google Account (or use an account you already signed in) and then for permission to create a file in your Google Drive. Grist will be able to create new files and manage them but will not be able to access any other files in your drive. Once you accept the permission request, Grist will export your document to an Excel file and then save it in your Google Drive as a Google Sheet file. To use this option, you need to have full read access for all tables in a document.","title":"Sending to Google Drive"},{"location":"exports/#backing-up-an-entire-document","text":"Grist makes regular backups of documents automatically, as described in Automatic Backups . You can also make manual backups by saving copies of documents in your Grist account. In addition, Grist documents can be downloaded in their entirety as an SQLite database file with a .grist extension. SQLite is a popular database format. The downloaded file will contain all your tabular data, any attached files within those tables, metadata about your tables, pages, and widgets, and a history of recent modifications of the document. It will not contain information about who the document is shared with. To download a Grist document, click the sharing icon ( ) on the top right of the screen, and select \u201cDownload\u201d.","title":"Backing up an entire document"},{"location":"exports/#restoring-from-backup","text":"A downloaded .grist file can be uploaded again to provide an exact copy of the original. To upload the file, open the team or personal site where you want to place it, and optionally select also a workspace. Then click on \u201cAdd New\u201d in the top left, and select \u201cImport document\u201d. You may also import CSV and Excel files as new Grist documents this way.","title":"Restoring from backup"},{"location":"automatic-backups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Backups # Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year. Examining Backups # To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time. Restoring an Older Version # While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option. Deleted Documents # When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Automatic backups"},{"location":"automatic-backups/#automatic-backups","text":"Grist automatically saves backups of your documents as you work on them. These backups \u2013 or snapshots \u2013 of the document can be examined at any time. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. For example, hourly snapshots are retained for about a day, but monthly snapshots are retained for more than a year.","title":"Automatic Backups"},{"location":"automatic-backups/#examining-backups","text":"To see the list of backups, click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel that opens on the right: You can open any of the listed snapshots to see the document as it existed at that time.","title":"Examining Backups"},{"location":"automatic-backups/#restoring-an-older-version","text":"While examining a snapshot, the \u201cShare\u201d menu has additional options; Use the \u201cReplace Current Version\u201d option to revert your document to the version you are looking at. You can also save the snapshot as a new document using the \u201cSave Copy\u201d option.","title":"Restoring an Older Version"},{"location":"automatic-backups/#deleted-documents","text":"When you delete a document, its history of backups is deleted with it. While the document is in Trash, you can still restore it for 30 days. If you do, you will find all the historical snapshots still there. If you choose \u201cDelete Forever\u201d for a document in Trash, or when a document in Trash is automatically purged after 30 days, the backups will be deleted forever with it.","title":"Deleted Documents"},{"location":"document-history/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Document history # To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d. Snapshots # Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document. Activity # The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Document history"},{"location":"document-history/#document-history","text":"To access information about a document\u2019s history, click \u201cDocument History\u201d in the left panel. The right panel will then offer two tabs, \u201cActivity\u201d and \u201cSnapshots\u201d.","title":"Document history"},{"location":"document-history/#snapshots","text":"Grist automatically saves complete backups of your documents as you work on them, making them available on the Snapshots tab. Read Automatic Backups for details on using this tab to examine, compare, and restore from older versions of a document.","title":"Snapshots"},{"location":"document-history/#activity","text":"The Activity tab lists all recent changes for the table associated with the widget you\u2019re currently looking at. Select the \u201cAll tables\u201d checkbox to see recent changes in the document regardless of table. Changes are shown as small table extracts. The cells are clickable, and bring you to the current version of the cell (if it still exists) in the \u201cprimary\u201d widget for the table containing it (this is typically the first widget created showing the table).","title":"Activity"},{"location":"workspaces/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Workspaces # Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want. Managing access # On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Workspaces"},{"location":"workspaces/#workspaces","text":"Documents can be collected in folders called \u201cworkspaces\u201d which can be shared as a single unit with other users. This is convenient, for example, for documents related to a single project. When a site is first created, it has a single workspace called \u201cHome.\u201d You can rename that workspace if you like, by hovering over the workspace name on the left bar, clicking on the three-dots icon, and selecting \u201cRename\u201d. Even better, you can create new workspaces, so you can group your documents and share them as a unit. To add a document to a specific workspace, click on that workspace in the left bar, then select \u201cAdd New\u201d, then \u201cCreate empty document\u201d (or \u201cImport document\u201d). To move a document from one workspace to another, hover over the document, click on the three-dots icon to the right of the document\u2019s name, and select \u201cMove\u201d. You\u2019ll then have the option to pick the workspace you want.","title":"Workspaces"},{"location":"workspaces/#managing-access","text":"On team sites , workspace owners can control who has access to a workspace using \u201cManage Users.\u201d The controls are just like for sharing documents . Workspaces in personal sites cannot be shared. Viewers of a team site will also be viewers of all its workspaces. Likewise for editors and owners. To exclude a workspace from inheriting team site viewers, editors, and owners, set \u201cInherit Access\u201d to \u201cNone\u201d. You can also set it to \u201cView Only\u201d to limit inheritance to view rights, or \u201cView & Edit\u201d to limit inheritance to view and edit rights (excluding the right to control sharing options). It is possible to be an editor/owner of a workspace and not be able to open all documents within that workspace. This can happen if an owner of a document limits inheritance to \u201cNone.\u201d You will still see the document listed, so that if you ever decide to delete the workspace you\u2019ll know what you\u2019re deleting. Viewers of a workspace will only see documents they have access to listed.","title":"Managing access"},{"location":"enter-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Entering data # A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell. Editing cells # While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell. Copying and pasting # You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted. Data entry widgets # In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu. Linking to cells # You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Entering data"},{"location":"enter-data/#entering-data","text":"A spreadsheet-like grid is a great way to see data. In Grist, this is the view offered by the default page widget called \u201cTable\u201d. As in a spreadsheet, you can use the mouse or arrow keys to move around the cells of a table. To start entering data into a selecte cell, either start typing, hit Enter , or double-click the cell.","title":"Entering data"},{"location":"enter-data/#editing-cells","text":"While editing a cell, several keys are special: Escape cancels the operation and restores the previous value in the cell. Tab , Shift + Tab saves your entry and moves your cursor to the next or previous cell. Enter saves your entry and moves your cursor to the next row. Shift + Enter adds a newline inside your cell.","title":"Editing cells"},{"location":"enter-data/#copying-and-pasting","text":"You can copy data from Grist or paste data into it. If the pasted range is longer than the available records, new records will be added. Note that Grist does not create new columns automatically. If the pasted data has more columns than the grid displays, extra columns will be omitted.","title":"Copying and pasting"},{"location":"enter-data/#data-entry-widgets","text":"In Grist, columns have types. In addition to typing in values, many column types offer specialized widgets for entering data more conveniently. Here are some of the most useful ones: Toggle . A \u201cToggle\u201d column shows True/False values, and can show them as a \u201cCheckbox\u201d or as a \u201cSwitch\u201d widget, which you can select in the column options. You can toggle a value in such a cell by clicking the check mark or the switch, or by hitting Space . Date and DateTime . Hitting Enter on such a cell will open a calendar to pick a date. Choice and Reference . Typing into a cell of one of these types will produce an auto-complete dropdown menu.","title":"Data entry widgets"},{"location":"enter-data/#linking-to-cells","text":"You can make a sharable link to a cell by pressing \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows) while the cell is selected. This option is also available via the row menu as \u201cCopy anchor link.\u201d The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Linking to cells"},{"location":"page-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Pages & widgets # Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs. Pages # In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon. Page widgets # A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu. Widget picker # The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts . Changing widget or its data # If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description. Renaming widgets # You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page . Configuring field lists # Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Pages & widgets"},{"location":"page-widgets/#pages-widgets","text":"Unlike traditional spreadsheets, in Grist you can create multiple views of the same data, and display multiple data sets on one page. This allows you to create useful dashboards and custom applications tailored to your needs.","title":""},{"location":"page-widgets/#pages","text":"In Grist, you organize your document into \u201cpages\u201d. These are listed in the left panel, with collapsible groups. You may rearrange and group pages in the left panel by dragging them to suit your needs. You can rename, remove, or duplicate pages using the three-dots menu next to the page name in the list. Renaming the page does not edit data tables\u2019 names or widget titles. See changing widget below to learn how to edit table and widget names. Duplicating a page duplicates views of data and does not duplicate the data itself. Removing a page does not delete data. When removing the last view of data, you will be asked if you want to delete only the view, but not the data itself; or if you want to delete both the page and the underlying data table(s). Learn more about your document\u2019s data in the raw data page . Note that pages can also be renamed by clicking the page name on top of the screen. Using the opener icon ( ) near the top of the left panel, you can collapse the panel to show only the initials of each page, leaving more screen space to view your data. To add a new page, use the \u201cAdd New\u201d button, and click \u201cAdd Page\u201d. At that point, you\u2019ll get to choose the page widget to include in the new page. Using Emojis in Page and Widget Names You can add any emoji to a Page or Widget name. The keyboard shortcut to open the emoji keyboard is Windows Logo + . (period) on PC or Command + Control + Space on Mac. You can also copy/paste an online source like Emojipedia . When a Page name starts with an emoji, it will replace the page icon.","title":"Pages"},{"location":"page-widgets/#page-widgets","text":"A page contains sections, such as tables or charts, which we call \u201cpage widgets\u201d. Each page widget shows data from one table. A page may contain more than one page widget, and you can arrange and link them to create useful layouts. Here are the supported kinds of page widgets. The salient features of each one are described on separate pages. Table : similar to the spreadsheet grid and a good way to see many records at once. Card : shows a single record in a form-like layout which you can customize. Card List : uses the same layout options as a card, displays a scrollable list of records. Chart : plots data on a chart with support for several different chart types. Calendar : displays event data in a calendar view. Custom : inserts a custom webpage, optionally granting it access to the document\u2019s data. There is a special page called raw data that lists all data tables in your document and summarizes your document\u2019s usage statistics. Navigate to the raw data page by clicking on the Raw Data link in the bottom left of the pages menu.","title":"Page widgets"},{"location":"page-widgets/#widget-picker","text":"The menu opened by the \u201cAdd New\u201d button has options \u201cAdd Page\u201d and \u201cAdd Widget to Page\u201d. In either case, you\u2019ll see the \u201cpage widget picker\u201d where you can choose your desired widget: You can select the type of widget and the table of data to show (or \u201cNew Table\u201d to create a new table). The \u201csummary\u201d icon ( ) allows you to summarize data . When adding a widget to an existing page, you\u2019ll also see a \u201cSelect By\u201d option, which allows linking this widget to another one already on the page. This process is described in greater detail in Linking widgets . Once you\u2019ve added widgets, they can be moved around and resized, as described in Custom Layouts .","title":"Widget picker"},{"location":"page-widgets/#changing-widget-or-its-data","text":"If you\u2019d like to change a widget or the data it displays after it\u2019s added, you may do so. Click the three-dots button on the top right of your widget, and select \u201cWidget options\u201d. This opens the right-side panel. Click on \u201cChange Widget\u201d. You can then use the widget picker to change the widget or the data it displays. You may also edit the widget\u2019s title or add a description.","title":"Changing widget or its data"},{"location":"page-widgets/#renaming-widgets","text":"You can rename widgets in several ways. We saw in the section above that you can edit a widget\u2019s title or add a description from the Widget tab of the Creator Panel. Another way is to click on the widget title above a widget. From here, you can edit the widget\u2019s title, the underlying data table\u2019s name or add a description. By default, the widget title is the data table\u2019s name. To override this, enter a new title under \u2018Widget Title\u2019. Learn more about data tables in the raw data page .","title":"Renaming widgets"},{"location":"page-widgets/#configuring-field-lists","text":"Although different kinds of page widgets look very different, they all represent a list of records. Any of the widget types can be used to show the same underlying data. In a Table , each record is represented by a row, and columns represent the same kind of value for each record. Note that the raw data page lists all data tables. In a Card List , each row of the underlying data is shown as a card. Each column in the data corresponds to a field in this card. When talking about a Card widget, we\u2019ll use the term \u201cfield\u201d , which conceptually is the same as a \u201ccolumn\u201d in a Table widget. A Card is just like a Card List, but shows only one row of data at a time. In a Chart , each row of the underlying data table becomes a graphical element, such as a point on a line chart, a bar in a bar chart, or a slice of a pie chart. In this context, the columns of our data table are better known as data \u201cseries\u201d . Click on the opener icon ( ) to open the right panel. Depending on the currently-selected widget, you might see a tab for configuring a Column, Field, or Series. These are not different in substance, but different terms make more sense for different widgets. Clicking on the widget tab (highlighted in green in the images above), you\u2019ll see subtabs for \u201cWidget\u201d, \u201cSort & Filter\u201d, and \u201cData\u201d. We\u2019ll focus on the first one: \u201cWidget\u201d. You\u2019ll see options specific to the type of the selected widget, and below that two lists: \u201cVisible Columns\u201d and \u201cHidden Columns\u201d. The \u201cHidden Columns\u201d are the columns available in the data, but not shown in this widget. In a Card, these lists would show up as \u201cVisible Fields\u201d / \u201cHidden Fields\u201d. In a chart, they show up as \u201cVisible Series\u201d / \u201cHidden Series\u201d. These lists allow you to include, exclude, or rearrange fields in a widget. As you move your mouse over the items in the list, use the \u201ceye\u201d icons that pop up to show or hide them. Alternatively, you can select several items using the checkboxes, and hide or show them together. This ordered list of fields can be used to customize any of the page widget types. It has a particular importance in the Chart widget , where different chart types and options require you to place series in a certain order in the \u201cVisible Series\u201d list to ensure your data is plotted correctly.","title":"Configuring field lists"},{"location":"raw-data/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Raw data # The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on. Duplicating Data # Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier. Usage # Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Raw data"},{"location":"raw-data/#raw-data","text":"The raw data page is a special page that lists all data tables in your document and summarizes your document\u2019s usage statistics. From your document, navigate to the raw data page by clicking on the \u2018Raw Data\u2019 link in the bottom left of the pages menu. Unlike other pages , the layout in the raw data page cannot be customized. From the list of data tables, you can find the data table\u2019s name and id, and remove data. Note that removing a data table from this page will delete data and remove it from all pages. This is different from other pages where it is possible to remove a view of data and not delete the data itself. Click on a data table to open it. Note that in the creator panel the widget type cannot be changed. Renaming the widget also renames the data table. Because raw data is intended to show all data, columns cannot be hidden, either. However, columns can be rearranged, deleted, created, and modified. For creators, this view may make it easier to edit data structure, add formulas , conditional formatting , and so on.","title":"Raw data"},{"location":"raw-data/#duplicating-data","text":"Tables can be duplicated from the \u2018Raw Data\u2019 page. Click the three-dot icon to the right of the table you wish to duplicate then select \u2018Duplicate Table\u2019 from the menu. By default, a duplicated table will only contain the table structure, not the data. If you wish to copy all data in addition to the table structure, be sure to check the box prior to clicking \u2018Save\u2019. If the original table has any access rules, those rules will not be duplicated. Only the document\u2019s default rules will apply to the copied table. The duplicate table is a new table that is not linked to the original. Meaning, if you update data in the copy, the original table will not be updated, and vice versa. Note that instead of duplicating tables, it\u2019s usually better to segment data by adding a new column. Let\u2019s use expenses as an example. Rather than having separate tables for each month\u2019s expenses, it is better to include all data in a single table and create a new column called Month to segment rows into months. In general, if you have multiple tables with near identical columns, this is an indicator that the data could all be in the same table. Doing so may make data analysis easier.","title":"Duplicating Data"},{"location":"raw-data/#usage","text":"Usage statistics are summarized beneath the list of data tables. Note that usage applies to the entire document, not individual tables. Learn more about document limits .","title":"Usage"},{"location":"search-sort-filter/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Search, Sort, and Filter # Grist offers several ways to search within your data, or to organize data to be at your fingertips. Searching # At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page. Sorting # It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior. Multiple Columns # When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority. Saving Sort Settings # Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount. Sorting from Side Panel # You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options. Advance sorting options # The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 . Saving Row Positions # When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them. Filtering # You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d. Range Filtering # Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available. Pinning Filters # Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing. Complex Filters # To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Search, sort & filter"},{"location":"search-sort-filter/#search-sort-and-filter","text":"Grist offers several ways to search within your data, or to organize data to be at your fingertips.","title":"Search, Sort, and Filter"},{"location":"search-sort-filter/#searching","text":"At the top of the document screen, you\u2019ll see a magnifying glass icon ( ) which opens a spreadsheet-like search. You can also start searching using the keyboard shortcut \u2318 F (Mac) or Ctrl + F (Windows). As you type into the search textbox, Grist will move the cursor to the next cell containing your search text. If there are no more matches in the current widget or page, the cursor will move to the next widget or switch to the next page.","title":"Searching"},{"location":"search-sort-filter/#sorting","text":"It\u2019s easy to find a word in a dictionary, but it wouldn\u2019t be if the words weren\u2019t sorted! Grist offers flexible and convenient sorting options, which are quite different from a traditional spreadsheet. To sort a table in Grist by a column, open the menu from the column\u2019s header and select \u201cSort\u201d: In the same menu item, you can choose to sort in ascending or descending order. Difference from Excel : Importantly, sorting does not change any formula calculations, it only changes the order in which you see the records. This is different from spreadsheets like Excel. Another difference is that the sort setting is \u201cactive\u201d: if you add a record, or change a cell that\u2019s used for sorting, the record will jump into its correct position in the sort order. See Saving Row Positions below for an option more similar to Excel\u2019s behavior.","title":"Sorting"},{"location":"search-sort-filter/#multiple-columns","text":"When a table is sorted, you can click another column to add it to the sort: The second column determines the order of records whose values in the first column are the same. You can add more columns to the sort. For example, you could: Sort employees first by Department, then by Last Name, then by First Name. Sort transactions first by Date, then by Amount. Sort tasks first by \u2018Is Completed\u2019 column, then by Priority.","title":"Multiple Columns"},{"location":"search-sort-filter/#saving-sort-settings","text":"Because sorting only affects your view of data, you can sort data in a Grist document even if you do not have edit access to it. If you can write to a document, then you can also save sort settings. When you change sorting on a table, the icon on top of it gets highlighted in green. Click it to see the currently active setting, as well as the options to manipulate them (e.g. remove a column from the sort), to save the setting, or to revert your changes: When you save sort settings, they will apply to the widget any time this page is opened, either by you or other collaborators. Multiple Views : Just like you can create multiple pages showing data from the same table, you can have different sort settings on each view of the data. E.g. you could have one view showing credit card transactions sorted by date, and another showing them sorted by amount.","title":"Saving Sort Settings"},{"location":"search-sort-filter/#sorting-from-side-panel","text":"You can sort widgets other than Table, such as Card List or Chart, using the \u201cSort & Filter\u201d subtab in the side panel: You can add one or more columns, save or revert settings and use advanced sorting options.","title":"Sorting from Side Panel"},{"location":"search-sort-filter/#advance-sorting-options","text":"The \u201cSort & Filter\u201d subtab exposes additional sorting options that might be useful for various column types: Use choice position \u2013 available on Choice column, sorts records using the configured order of choice items, rather then their names. Empty values last \u2013 in alphabetical order empty values are shown first by default, use this option to put them at the end. Natural sort \u2013 for Text column will treat multi-digit numbers in strings as if they were a single character, allowing more human-friendly ordering. For example, using natural sort , Product10 will be positioned after Product2 .","title":"Advance sorting options"},{"location":"search-sort-filter/#saving-row-positions","text":"When you use the side panel, you\u2019ll notice one extra button: \u201cUpdate Data\u201d. If you click it, the current position of the records relative to each other is saved, and the active sort by your selected columns is turned off. In other words, rows will no longer jump into place according to values in certain columns, but will remain in the place they are in. You will also be able to reorder rows manually by dragging them.","title":"Saving Row Positions"},{"location":"search-sort-filter/#filtering","text":"You can choose to see only a subset of data in a table by filtering for certain values in a column. Open the column menu from any column\u2019s header, and click \u201cFilter Data\u201d. You\u2019ll see a dialog listing the values in the column: Uncheck the values you don\u2019t want to see, or click \u201cNone\u201d and then check only the values you do want to see. The search bar at the top of the dialog lets you find values of interest if the list is long. Click \u201cApply\u201d to apply your setting. You can filter by more than one column. Only those rows will appear which match all of the filters. As with sorting, you can save filters to the view. Click the highlighted \u201cSort & Filter\u201d icon on top of the table: You\u2019ll see the columns for which there are active filters, and options to save the filter, or to revert to saved settings. As with sorting, you can create different widgets or pages showing the same table with different filter settings. For example, you could have one page showing event attendees with status \u201cConfirmed\u201d and another one showing those with status \u201cPending\u201d.","title":"Filtering"},{"location":"search-sort-filter/#range-filtering","text":"Certain column types allow for filtering within a range of values. When filtering a numeric or integer type column, you have the option to filter within a range of numbers. For example, if you only want to see high-value purchases in a table of credit card transactions , you could add a filter for the amount column and enter a minimum value of $500. The table would be filtered to show any transactions with an amount of $500 or greater. When filtering a date or datetime type column, you have the option to filter within a specified date range. The range can be entered into the \u201cStart\u201d and \u201cEnd\u201d fields, or selected on a calendar. Perhaps you went on vacation and want to see all transactions during that time. We could add a filter to the Date column to see all transactions within the date range of the trip. You can also filter date and datetime type columns by a relative range, which is a range relative to today\u2019s date. For example, you might want to filter for tasks due in the next 14 days. That filter would update each day so that it is always filtering by 14 days into the future, relative to today\u2019s date. When filtering a date or datetime column, there are several relative ranges suggested as a shortcut, as seen in the image below. If you use the calendar to select a range of dates, you can also convert each absolute date into a relative date by selecting from the list of relative dates available.","title":"Range Filtering"},{"location":"search-sort-filter/#pinning-filters","text":"Filters can be pinned at the top of a table for quick filtering. By default, filters will be pinned when first applied to a table. To unpin a filter, click the filter button then click the pin icon. If you unpin a filter button, any saved filters will still be applied but the button will no longer be visible. It may be useful to save the buttons without any filtering to create a quick filter toolbar. If you do save settings with filters applied, that saves it for future sessions and other team members will see the same thing.","title":"Pinning Filters"},{"location":"search-sort-filter/#complex-filters","text":"To filter for more complex conditions, create a new formula column and filter this column for values of \u201ctrue\u201d. For example, to filter for \u201cPersons with salary over $100,000 or a position of \u2018Board Member\u2019\u201c, you can use a formula like this: $Salary > 100000 or $Position == 'Board Member' This will produce a column of true and false values, which you can filter for the value true .","title":"Complex Filters"},{"location":"widget-table/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Table # The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know. Column operations # Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!) Row operations # Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document. Navigation and selection # Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range. Customization # Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Table widget"},{"location":"widget-table/#page-widget-table","text":"The Table widget is a versatile spreadsheet-like grid. Here are some useful features to know.","title":"Page widget: Table"},{"location":"widget-table/#column-operations","text":"Resize columns : Click on the line between column headers, and drag it to resize columns. Reorder columns : With a column selected, drag its header to move it to a different place relative to other columns. (You can also do this by reordering fields in the widget options panel.) Rename columns : With a column selected, click its header to rename it. Hit Enter to save the new name. Add columns : Click the \u201c+\u201d icon on the right of all the column headers to add a new column, or show any of the hidden columns. The column menu also allows inserting a new column next to an existing column, as do the keyboard shortcuts Alt + + (insert before) and Alt + = (insert after). After adding a column, the column name (set by default to \u201cA\u201d, \u201cB\u201d, \u201cC\u201d, etc.) is immediately selected and highlighted to let you quickly rename it. Just type in the new name and hit Enter , or hit Escape to keep the default name. Hide columns : Move the mouse over the column header and click the triangle to open the column menu. Click \u201cHide column\u201d to hide the column. The column remains in the underlying data, and can be shown again using the \u201c+\u201d icon on the right of the column headers, or the field list in the widget options panel. Delete columns : Delete the actual column of data using the column menu option, or the Alt + Minus keyboard shortcut. The table is the only widget that allows deleting a column. Note: deleting and hiding are different. Hiding a columns removes it only from the current page widget, but leaves it in the data and available to formulas. Deleting a column removes it from everywhere. (Of course, undo still works for either operation!)","title":"Column operations"},{"location":"widget-table/#row-operations","text":"Add rows : Type into the last row in a table, which is highlighted to indicate that it\u2019s a placeholder for adding new records. Right click a row number to insert a blank row next to an existing row, or use the keyboard shortcuts \u2318 \u21e7 = (Mac) or Ctrl + Shift + = (Windows) to insert before, and \u2318 = (Mac) or Ctrl + = (Windows) to insert after. Delete rows : Right click a row number and select the \u201cDelete\u201d option to delete a row, or use the \u2318 + Minus (Mac) or Ctrl + Minus (Windows) shortcut. If you select a range of cells first, either of these delete actions will delete all rows included in the range. Link to rows : Right click a row number and select \u201cCopy anchor link\u201d to copy a link to the selected cell of that row. The link will be placed in your clipboard, ready to paste into an email or an instant messaging app. The link will open only for people with access to the document.","title":"Row operations"},{"location":"widget-table/#navigation-and-selection","text":"Navigate using shortcuts : Use keyboard shortcuts to navigate the grid: Tab , Shift + Tab Move to the next or previous column, saving changes if editing a cell. \u2318 + Up (Mac) or Ctrl + Up (Windows) Move up to the first row. \u2318 + Down (Mac) or Ctrl + Down (Windows) Move down to the last row. Home or Fn + \u2190 (Mac) Move to the beginning of a row. End or Fn + \u2192 (Mac) Move to the end of a row. PageDown or Fn + \u2193 (Mac) Move down one page of rows. PageUp or Fn + \u2191 (Mac) Move up one page of rows. Alt + Down , Alt + Up Move down or up five rows. Select ranges : Click and drag a mouse across the grid to select a range of cells to copy (copy and paste using the usual keyboard shortcuts for your computer). Another way to select a range is to click one cell, and then hold Shift while clicking another cell, or while navigating with the arrow keys. Fill down data : Select a range of cells, and hit \u2318 + D (Mac) or Ctrl + D (Windows) to fill the whole selected range with the values of the cells in the top row of the range.","title":"Navigation and selection"},{"location":"widget-table/#customization","text":"Customize table looks : In the widget options panel, you can turn off horizontal or vertical grid lines, or turn on zebra striping. For example, this lets you change the look of your grid to a list like this:","title":"Customization"},{"location":"widget-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Card & Card List # The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one. Selecting theme # The widget options panel allows choosing the theme, or style, for the card: Editing card layout # To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget. Resizing a field # To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents. Moving a field # To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location. Deleting a field # To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Adding a field # To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active. Saving the layout # When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Card & card list"},{"location":"widget-card/#page-widget-card-card-list","text":"The Card and Card List widgets allow viewing data as a form or a record, similar to how data is commonly presented in a custom application. In Grist, you can choose what fields to show and how to lay them out. A Card List shows a scrollable list of cards. A Card shows one at a time. A single Card is most useful when linked to another Table widget (see Linking widgets ). It can then show the details of the selected record. The single Card widget may also be used on its own. In this case, you\u2019ll see navigation buttons on top to move to the next or previous record, or to add a new one.","title":"Page widget: Card"},{"location":"widget-card/#selecting-theme","text":"The widget options panel allows choosing the theme, or style, for the card:","title":"Selecting theme"},{"location":"widget-card/#editing-card-layout","text":"To configure the layout of a card, click the green \u201cEdit Card Layout\u201d button below the theme selector, or click the three dots on the top right of the widget and select \u201cEdit Card Layout\u201d in the menu. While the layout editor is active, you\u2019ll see a single record with draggable fields, and a set of buttons on top of the widget.","title":"Editing card layout"},{"location":"widget-card/#resizing-a-field","text":"To resize a field, move the mouse to a vertical border separating two fields and drag the border to your desired size. Only the width of fields may be changed, while the height changes dynamically to fit the field contents.","title":"Resizing a field"},{"location":"widget-card/#moving-a-field","text":"To move a field, hold down the mouse button on a field and drag it. As you move the mouse near the edges of other fields, you will notice boxes with dashed borders indicating possible drop points in relation to other fields. Release the mouse over one of these boxes to place your field into the desired location.","title":"Moving a field"},{"location":"widget-card/#deleting-a-field","text":"To delete a field, move the mouse over it. An \u201cx\u201d icon will appear. Click this icon to remove the field. This is similar to hiding a column as removing a field from a widget does not delete the underlying data. You can also delete a field by hiding it in in the \u201cVisible Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Deleting a field"},{"location":"widget-card/#adding-a-field","text":"To add a field, click the \u201cAdd Field\u201d button on top of the layout editor. In the dropdown menu, select \u201cCreate New Field\u201d to create a new column of data, or one of the existing fields in the table that are not currently shown in the card. When you click on the field, it will be added at the bottom of the layout. You can then move it or resize it as described above. Any new fields you create will only be added to the data when you save the layout. You can rename these in the \u201cField\u201d tab in the side panel. You can also add an existing field by making it visible using the \u201cHidden Fields\u201d list in the side panel, as described in Configuring field list . This is available even when the layout editor is not active.","title":"Adding a field"},{"location":"widget-card/#saving-the-layout","text":"When done editing the layout, remember to click \u201cSave Layout\u201d on top of the widget, or \u201cCancel\u201d to revert your changes. Once saved, the Card or Card List widget will update to show all cards in the updated layout.","title":"Saving the layout"},{"location":"widget-form/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Form # The form widget allows you to collect data in a form view which populates your Grist data table upon submission. Setting up your data # Create a table containing the columns of data you wish to populate via form. Creating your form # Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table. Adding and removing elements # To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon. Configuring fields # You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field. Configuring building blocks # Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like
    and

    from other elements using blank lines. Configuring submission options # You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel. Publishing your form # Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error. Form submissions # After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form"},{"location":"widget-form/#page-widget-form","text":"The form widget allows you to collect data in a form view which populates your Grist data table upon submission.","title":"Page widget: Form"},{"location":"widget-form/#setting-up-your-data","text":"Create a table containing the columns of data you wish to populate via form.","title":"Setting up your data"},{"location":"widget-form/#creating-your-form","text":"Add a form widget from the \u201cAdd New\u201d menu. Select the data table you wish to populate with form data. Then, customize the form to your heart\u2019s desire! By default, the form view will include elements for headers and descriptions as well as all columns (fields) from the underlying data table.","title":"Creating your form"},{"location":"widget-form/#adding-and-removing-elements","text":"To add additional form elements, click the + icon at the bottom of the form. From the menu, you can add the following elements: New Question: Select a column type to create a new field. \u201c\u2022\u2022\u2022 More >\u201d will open an expanded menu listing all column types. Adding a new question will add a new column to the underlying data table. Unmapped Fields: Lists any hidden fields from the underlying data table. Building Blocks: Customize further by adding these additional elements! You can remove any element from the form by hovering over the object and clicking the trash icon to delete. You can hide any uneccessary fields from the form by hovering over the object and clicking the x icon.","title":"Adding and removing elements"},{"location":"widget-form/#configuring-fields","text":"You can provide alternative titles for your form fields, rather than use the same column name from the underlying data table. For example, on our form, we have a toggle that is titled \u201cMay we contact you?\u201d. In the data table, this column is labeled \u201cOk to Contact?\u201d. Field titles can be configured under the \u201cField\u201d tab of the creator panel. To make a form field required, check the box next to \u201cRequired field\u201d. If a user attempts to submit a form without filling in the required field, they will get an alert to fill out the field.","title":"Configuring fields"},{"location":"widget-form/#configuring-building-blocks","text":"Header and Paragraph building blocks can be edited either directly in the block or from the creator panel. In the creator panel, you have text alignment options available. For additional formatting, both elements allow the use of Markdown formatting. For help on Markdown formatting, check out the Markdown Guide . HTML Formatting HTML tags can be used in Markdown-formatted text. Be sure to separate block-level HTML elements like

    and

    from other elements using blank lines.","title":"Configuring building blocks"},{"location":"widget-form/#configuring-submission-options","text":"You also have the option to configure different settings for the \u201cSubmit\u201d button. You can change the button label, update the success text which appears after a form is submitted and choose to allow multiple form submissions. You also have the option to select a specific URL to redirect to following submission. These options are all available under the \u201cForm\u201d tab and \u201cSubmission\u201d subtab of the creator panel.","title":"Configuring submission options"},{"location":"widget-form/#publishing-your-form","text":"Once you have finished customizing your form, you have the option to preview your form, prior to publishing. Previewing will allow you to see what your form will look like to end users, without making it available for use. After you have confirmed everything is as you wish, you can publish your form. Note that only users with \u201cOWNER\u201d access have permission to publish a form. The first time you publish a form, the following informational popup will appear, explaining the permissions a published form grants. Once a form has been published, a button will appear to copy the link to the form. Share this link with end users to fill out your form! You also have the option to unpublish your form. Note that unpublishing the form will disable the share link. Users accessing the form via that link would then see an error.","title":"Publishing your form"},{"location":"widget-form/#form-submissions","text":"After sharing the link to your published form, end users can submit data to your Grist document via the form. All submitted data will appear in your underlying data table in your Grist document! Collecting data has never been so easy. \ud83d\ude0d","title":"Form submissions"},{"location":"widget-chart/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Chart # Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here: Chart types # Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts. Bar Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox. Line Chart # Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Pie Chart # Needs pie slice labels and one series for the pie slice sizes. Area Chart # Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines. Scatter Plot # Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points. Kaplan-Meier Plot # The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis. Chart options # A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart"},{"location":"widget-chart/#page-widget-chart","text":"Grist supports several chart types to help you visualize your data easily. Charts may be used to plot a regular table of data, a linked widget (as described in Linking widgets ), or a summary table (as described in Summary tables ). The most common chart types are illustrated here:","title":"Page widget: Chart"},{"location":"widget-chart/#chart-types","text":"Each chart type plots one or several data series. Select the series to plot by clicking the green \u2018Add Series\u2019 text in the creator panel . In the area above the \u201cSeries\u201d section of the creator panel, you may configure the x-axis for most charts, or the labels for pie and donut charts.","title":"Chart types"},{"location":"widget-chart/#bar-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series create additional bars at each point on the x-axis. To stack series onto the same bar, select the \u201cStack series\u201d checkbox.","title":"Bar Chart"},{"location":"widget-chart/#line-chart","text":"Needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Line Chart"},{"location":"widget-chart/#pie-chart","text":"Needs pie slice labels and one series for the pie slice sizes.","title":"Pie Chart"},{"location":"widget-chart/#area-chart","text":"Similar to a line chart, needs an x-axis and at least one series to plot along the y-axis. Additional series specify Y values for additional lines.","title":"Area Chart"},{"location":"widget-chart/#scatter-plot","text":"Needs a label and two or more series. The label applies to the points. The series apply the X and Y values for each point, respectively. Additional series specify Y values for additional sets of points.","title":"Scatter Plot"},{"location":"widget-chart/#kaplan-meier-plot","text":"The Kaplan-Meier Plot is useful for certain studies, and needs one label and one series. The label applies to the lines being plotted. The series gives a survival time or time-to-failure of that point. The plot shows the survival times on the X axis, and the number of points that survive at that time on the Y axis.","title":"Kaplan-Meier Plot"},{"location":"widget-chart/#chart-options","text":"A number of chart options are available, some of them specific to certain chart types. Split Series : When checked, an extra series is to be selected under the \u2018Split Series\u2019 dropdown. The series should contain a group label for each data point. All points with the same group value are plotted as a separate line. For example: Invert Y-axis : When checked the Y axis is flipped, with smaller values above and larger values below. Connect gaps [for Line Charts only]: When checked, gaps caused by missing values are connected by connecting neighboring points. The \u201cShow Markers\u201d option described next can be used to keep a visual cue for which points are present. Show markers [for Line Charts only]: When checked, each point on the line is marked additionally by a small circle. See the example for Split Series above. Stack series [for Line and Bar Charts]: When checked, split series will be stacked, rather than shown separately, giving a total for your selected series. In this example, we can see the total revenue for each month across all three departments. Note that \u2018Split series\u2019 must be checked in order to select multiple series to stack. Error bars [for Line and Bar Charts]: When set to \u201cSymmetric\u201d, each Y series should be followed by a series for the length of the error bars to show. When set to \u201cAbove+Below\u201d, each Y series should be followed by two series, one for the top error bars, and one for the bottom. In the example here, \u201cSplit Series\u201d is checked. So the series selected from the Split Series dropdown (\u201cCell Line\u201d), specifies how to group the data into lines. The series selected under the X-Axis dropdown (\u201cLog[Drug], uM\u201d), specifies our values along the x-axis. Our first series in the Series list (\u201c% Viable Cells\u201d), specifies our y-axis values, and the series that follows that (\u201cSD\u201d), specifies the error bars for those Y values.","title":"Chart options"},{"location":"widget-calendar/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Calendar # The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data. Setting up your data # In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling. Configuring the calendar # Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional). Adding a new event # You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent! Linking event details # It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts . Deleting an event # To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Calendar"},{"location":"widget-calendar/#page-widget-calendar","text":"The calendar widget allows viewing data in a calendar view. In Grist, event data is stored in a data table. Then, the calendar widget can be configured to display that data.","title":"Page widget: Calendar"},{"location":"widget-calendar/#setting-up-your-data","text":"In your data table, you will need two columns of data, with the option to add three additional data columns: Title : Text column, containing the title of your event. Start Date : Date or DateTime column, containing the date, or date and time, that the event begins. (Optional) End Date : Date or DateTime column, containing the date, or date and time, that the event ends. (Optional) Is All Day? : Toggle column, noting if an event is all day long. (Optional) Type : Choice or Choice List column, containing the event category and styling.","title":"Setting up your data"},{"location":"widget-calendar/#configuring-the-calendar","text":"Add a calendar widget from the \u2018Add New\u2019 menu. Select the table containing your event data. Configure the widget by selecting the columns in your data table that contain Start Date, End Date (optional), Is All Day? (optional), Title and Type (optional).","title":"Configuring the calendar"},{"location":"widget-calendar/#adding-a-new-event","text":"You can add a new event by double-clicking the start time for the event in the calendar widget. In the popup, you can add a title for the event and modify the start and end time. You also have the option to mark the event as \u2018all day\u2019. You can also modify the start and end time of an event by clicking and dragging the event directly on the calendar. To modify an event\u2019s start time, click and drag from the middle of the event. When modifying start time, the duration of the event will remain the same. To modify an event\u2019s end time, click and drag from the bottom of the event. You\u2019ll notice that the icon is slightly different from the icon that appears when modifying start time. Any changes to start and end time will be made to the underlying data table so your data will always be consistent!","title":"Adding a new event"},{"location":"widget-calendar/#linking-event-details","text":"It might be useful to see more event details in a table or card widget. This example will walk through a card widget. To create a linked view, add a new type of widget such as a table or a card, and select the same data table. Under \u2018Select By\u2019, select the calendar widget. Then, add to page. Now, when you click on an event in the calendar widget, the linked widget will update to show the details for the selected event. Collapsing widgets Drag the linked widget into the attic at the top of the page to collapse. The widget will still be linked but will take up less space on the page! Simply click the box to expand and view linked record details. Read more on Custom Layouts .","title":"Linking event details"},{"location":"widget-calendar/#deleting-an-event","text":"To delete an event, double-click the event in the calendar then select \u2018delete\u2019 in the popup.","title":"Deleting an event"},{"location":"widget-custom/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Page widget: Custom # The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful. Minimal example # To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord

    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support. Adding a custom widget # To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html Access level # When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget. Invoice example # The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord . Creating a custom widget # As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API. Column mapping # Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping. Widget options # If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen. Custom Widget linking # Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'}); Premade Custom Widgets # All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown. Advanced Charts # The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration. Copy to clipboard # Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Dropbox Embedder # View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template. Grist Video Player # Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget. HTML Viewer # The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar . Image Viewer # View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar ! JupyterLite Notebook # This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook. Map # The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar . Markdown # The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar . Notepad # The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar . Print Labels # The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Custom"},{"location":"widget-custom/#page-widget-custom","text":"The Custom widget allows a user to insert almost anything in their document. To create a custom widget currently requires knowledge of web development, and access to a public web server (for example, GitHub Pages). A good use for custom widgets is to view records or tables in new ways. Using Grist as your data model and modern HTML/CSS/JS as your view is very powerful.","title":"Page widget: Custom"},{"location":"widget-custom/#minimal-example","text":"To demonstrate to a web developer how custom widgets work, there is a minimal working example at: https://public.getgrist.com/911KcgKA95oQ/Minimal-Custom-Widget/m/fork The example shows a table with some random data (names for pets), and two custom widgets, one showing the selected row in the table as JSON, and the other showing all rows of the table as JSON. If you change data in the table, or move the cursor, the custom widgets update as appropriate. The source code for the widgets is at: https://github.com/gristlabs/grist-widget/tree/master/inspect It is stripped down to the essentials. Here is the full source code of the onRecord widget that shows one row of data: onRecord
    Waiting for data...
    The \u201cGrist\u201d parts of this are: Including https://docs.getgrist.com/grist-plugin-api.js to get the Grist API. Calling grist.ready to let Grist know the widget is ready to go. Calling grist.onRecord to subscribe to the current row of the table. After that, everything else is regular HTML/CSS/JS. Once you have data coming in, you can render it any way you like, using React, Vue.js, or your favorite framework. For example, you could render records as a printable invoice , or use some obscure chart format that Grist doesn\u2019t currently support.","title":"Minimal example"},{"location":"widget-custom/#adding-a-custom-widget","text":"To add a custom widget that reads from a table, click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom to get a Custom Widget. For Select Data choose the table you want the widget to read data from. Optionally, choose Select By to control the selected data further (read Linking Page Widgets for the possibilities). The custom widget is initially blank. To configure it, click the three-dots button on the top right of the custom widget, and select \u201cWidget options\u201d. In the CUSTOM settings section where it says Enter Custom URL , put the link to your custom widget. Here is a test widget to simply show table data in JSON: https://gristlabs.github.io/grist-widget/inspect/onRecords.html And here\u2019s one to show the selected row only (make sure \u201cSelect By\u201d is set for the custom widget): https://gristlabs.github.io/grist-widget/inspect/onRecord.html","title":"Adding a custom widget"},{"location":"widget-custom/#access-level","text":"When you put a link to a custom webpage it will be immediately rendered inside the section. Now you have the option to grant that webpage access to data in your document. The following access levels are available: No document access : the webpage is shown in the widget, but it has no access to the Grist document containing the widget. Read selected table : the webpage is shown in the widget, and is given read access to the table the widget is configured to select data from. Full document access : the webpage is shown in the widget, and has full access to read and modify the Grist document containing the widget. The webpage should be owned and controlled by you or someone you trust. With Read selected table permissions, a widget could send the data it accesses to a third party. With Full document access permissions, a widget could send all the document data to a third party, and modify your document in any way. If you are writing your own custom widget you can specify what access level it needs as part of the initial ready message. Possible values are: none , read table and full . This directs Grist to request the desired access level from the user. Your widget will be reloaded with the appropriate access level if the user approves the request. If you wish to get notified of the access level, you can subscribe to the onOptions event that is sent to the widget after it tells Grist it is ready: grist.onOptions(function(options, interaction) { console.log(interaction.access_level); }); For now, just skip the options parameter (it will be described in Widget options section). The current access level is part of the second parameter, which describes how Grist will interact with your widget.","title":"Access level"},{"location":"widget-custom/#invoice-example","text":"The minimal example above showed records as plain JSON, but the widget can get as fancy as you like. Here is an example of showing a record as a printable invoice: You can read the details of how to use this widget in our Invoice preparation example . The invoice widget is hosted at: https://gristlabs.github.io/grist-widget/invoices/ And the source HTML/CSS/JS can be browsed at: https://github.com/gristlabs/grist-widget/tree/master/invoices It uses Vue.js and grist.onRecord .","title":"Invoice example"},{"location":"widget-custom/#creating-a-custom-widget","text":"As you saw, writing a simple widget that uses data from a table is very easy. First, you need to tell Grist that you are ready and then subscribe to one of the available events: onRecord , onRecords or onOptions . grist.ready(); grist.onRecord(function (record) { // Cursor has moved. }); grist.onRecords(function (record) { // Data in the table has changed. }); grist.onOptions(function (options, interaction) { // Configuration has changed. }); Let\u2019s say you want to build a custom widget that will show an image from a URL and optionally a single line of text below as the image title. You will need to read two values from two columns: Link and Title . You could access those columns directly using literal column names in your script. Here is a complete example of widget source code that will do the job:
    When getting started, this is a good approach, but it has two significant drawbacks. Every time you rename a column, you will also have to change your widget\u2019s source. Moreover, using this widget on a different table or sharing it with your friends can be difficult as column names might be different. To help with this, Grist offers the column mapping API.","title":"Creating a custom widget"},{"location":"widget-custom/#column-mapping","text":"Instead of using column names directly, you can ask the user to pick which column to use as a Link and Title . The list of expected columns can be sent to Grist as part of the ready call: grist.ready({columns: ['Link', 'Title']}); Using this information, in the creator panel, Grist will hide the regular \u201cVisible\u201d columns section and display specialized column pickers. Your widget will receive this mapping configuration as part of onRecord or onRecords event in the second parameter. You can use this configuration to do the mappings yourself or use the mapColumnNames helper function to do it for you.
    Now, if you rename one of the columns, the widget will still work. You can also use this widget in any other table or share with a friend, as it doesn\u2019t depend on your table structure and can be easily configured. In the configuration used above, we told Grist that all the columns are required, and the user can pick any column even if the column doesn\u2019t contain a text value. To be more precise, we can include more options in the request. For example: grist.ready({columns: [ { name: \"Link\", // What field we will read. title: \"Image link\", // Friendly field name. optional: false, // Is this an optional field. type: \"Text\" // What type of column we expect. description: \"Some text\" // Description of a field. allowMultiple: false // Allows multiple column assignment. } ]}); The optional setting is important for correct operation of the mapColumnNames helper. This helper will return a mapped record only when all required (not optional) columns are picked. By default Grist will allow the user to pick any type of column. To allow only a column of a specific type, you need to set a type property. Here are all valid types: Int ( Integer column ), Numeric ( Numeric column ), Text , Date , DateTime , Bool ( Toggle column ), Choice , ChoiceList , Ref ( Reference column ), RefList ( Reference List ), Attachments . The default value of type is Any , so Grist will allow the user to pick any column type. You can also specify a list of types, for example Date,DateTime . In that case, Grist will allow the user to pick any column that matches one of the types in the list. Use title and description fields to help your users understand what is the purpose of the column. The description will be displayed just below the column name, and the title will be used as a column label. Both are optional and you can put there any text you want. If you need to map multiple columns (for example in a custom chart widget), you can use allowMultiple option. This will allow your users to pick a set of columns that will be returned as list of mapped table column names. The mapColumnNames helper will then return an array of mapped column values in a single field. Suppose the user deletes a column or changes its type so that it will no longer match the type requested by the widget. In that case, Grist will automatically remove this column from the mapping.","title":"Column mapping"},{"location":"widget-custom/#widget-options","text":"If your widget needs to store some options, Grist offers a simple key-value storage API for you to use. Here are some JavaScript code snippets that show how to interact with this API: // Store a simple text value . await grist.setOption('color', '#FF0000'); // Store complex objects as JSON. await grist.setOption('settings', {lines: 10, skipFirst: true}); // Read previously saved option const color = await grist.getOption('color'); // Clear all options. await grist.clearOptions(); // Get and replace all options. await grist.getOptions(); await grist.setOptions({...}); You can experiment with this yourself. Here is a test widget that demonstrates how to use this API: https://gristlabs.github.io/grist-widget/inspect/onOptions.html When your widget saves or edits some options, the icon on top of the section gets highlighted in green. You can either apply those options to the widget or revert that modification. This allows viewers (users with read-only access) or collaborators to configure your widget without overwriting original settings. This behavior should look familiar to you and others, as this works like sorting and filtering on table or card views. Saving current options you will apply them to the widget and make them available to others. Using this menu, you can also clear all options to revert the widget to its initial state. To do this, press the little trash icon and then Save . Grist will also trigger an event, every time the options are changed (or cleared). Here is how you can subscribe to this event. grist.onOptions(function(options, interaction) { if (options) { console.log('Current color', options.color); } else { // No widget options were saved, fallback to default ones. } }); If you are building your own widget, you generally should not read options directly (using grist.widgetApi.getOption() ). A better pattern is to apply them all when they are changed. Using the onOptions handler will make your widget easier to change and understand later. There is one more scenario to cover. Suppose your widget has some kind of custom configuration screen. In that case, you probably need some button or other UI element that the user can use to show it. This additional UI element will likely be rarely used by you or your collaborators, so it doesn\u2019t make sense to show it all the time. To help with this, Grist offers an additional interaction option you can send as part of the ready message: grist.ready({ onEditOptions: function() { // Your custom logic to open the custom configuration screen. } }); This will tell Grist to display an additional button Open configuration in the creator panel and the section menu. When clicked, it will trigger your handler, which you can use to show your own custom configuration screen.","title":"Widget options"},{"location":"widget-custom/#custom-widget-linking","text":"Custom widgets can also be used as a source of linking (see Linking widgets ). All you need to do is inform Grist that your widget supports linking by passing an additional option to the ready call (see Widget API ): grist.ready({ allowSelectBy: true }); This will enable the Select By option in the widget configuration panel. Now you can use your widget to control the cursor position in linked widgets. To do this, you need to call the setCursorPos function: // Inform Grist that the cursor should be moved to the row with id 20. grist.setCursorPos({rowId: 20}); // or inform that your widget is creating a new row. grist.setCursorPos({rowId: 'new'});","title":"Custom Widget linking"},{"location":"widget-custom/#premade-custom-widgets","text":"All premade custom widgets are available in the Custom Widget configuration panel on the right-hand side of the screen under the Custom dropdown.","title":"Premade Custom Widgets"},{"location":"widget-custom/#advanced-charts","text":"The Advanced Charts custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. It\u2019s a version of Plotly\u2019s Chart Studio , see their tutorials for more detailed help. You\u2019ll need to set the access level to \u201cFull document access\u201d. Don\u2019t worry, the widget only reads data from the selected table, doesn\u2019t send it to any servers, and doesn\u2019t write or otherwise make changes back to your document. This is what you should see: Click the big blue \u201c+ Trace\u201d button to get started. This will add a panel like the following: Click \u201cScatter\u201d to choose a different chart type such as Bar or Line. Then click the \u201cChoose data\u201d dropdowns to select the columns you want to plot. You can add multiple traces to overlay different plots. Try different panels from the sidebar to customize the chart further. For example, go to Style > Axes > Titles to add a label to each axis. See the chart studio tutorials to learn more. As you customize the widget, remember to regularly click the \u2018Save\u2019 button above the widget to keep your configuration.","title":"Advanced Charts"},{"location":"widget-custom/#copy-to-clipboard","text":"Copy to clipboard copies a value from the specified column of the selected record. When configuring the widget, you will need to select which column you wish to copy data from. Note that you can also copy data from a selected cell by using the keyboard shortcut Ctrl + C on Windows or \u2318 + C on Mac. To paste, use Ctrl + V or \u2318 + V . You can find an example of the copy to clipboard button in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"Copy to clipboard"},{"location":"widget-custom/#dropbox-embedder","text":"View and access files saved to dropbox. To start, add a new column to your table to store your dropbox links. Then, add a new custom widget to the page. Choose the data table that contains the dropbox links and \u2018Select By\u2019 that same table. To configure, select \u2018Dropbox Embedder\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Dropbox Link\u2019, select the column that contains your dropbox links. You can create links to folders or specific files in Dropbox. Click \u2018Share\u2019 then set permissions for the link. You can choose to allow anyone with the link to view or edit. Create, then copy the link. Paste this link into your Dropbox Link column in Grist. Note that users cannot edit directly in the custom widget even if edit permissions are granted. To edit, select the object in the Dropbox Embedder and it will open in a new tab where it can be edited directly in Dropbox. You can check out an example of the Dropbox Embedder in our Hurricane Preparedness template.","title":"Dropbox Embedder"},{"location":"widget-custom/#grist-video-player","text":"Embed videos from online sources like YouTube, Vimeo, Facebook Video, Google Drive and more. To start, add a new column to your table to store your video URLs. Then, add a new custom widget to the page. Choose the data table that contains the video URLs and \u2018Select By\u2019 that same table. To configure, select \u2018Grist Video Player\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018URL\u2019, select the column that contains your video URLs. For most online videos, including YouTube videos and videos stored on Google Drive, you can simply click the \u2018Share\u2019 option and copy the URL. For some other videos, you may see this error: If this happens, you\u2019ll need to take the URL from the Embed code. After clicking the share option on the video, click the option to \u2018Embed\u2019. Then, click to copy the code. The code it gives you will look something like this: Copy the URL that is found between quotes following src . The highlighted portion in the screenshot below is what you would copy for this particular Facebook video. Paste this URL into your URL column in Grist and the video will now appear in the Grist Video Player custom widget.","title":"Grist Video Player"},{"location":"widget-custom/#html-viewer","text":"The HTML viewer displays HTML written in a cell. For text-editing widgets, check out our Markdown and Notepad custom widgets. To start, add a new column to your table. This will be where you add you write HTML. Then, add a new custom widget to the page. Choose the data table that contains the HTML and \u2018Select By\u2019 that same table. To configure, select \u2018HTML Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018HTML\u2019, select the text column that contains your HTML. Your HTML will be viewable in the custom widget. For help on HTML formatting, check out this guide from W3 Schools: HTML Text Formatting You can find an example of the HTML Viewer in our Webinar 7 (Custom Widgets) template. You can also watch a video walkthrough from our Custom Widgets Webinar .","title":"HTML Viewer"},{"location":"widget-custom/#image-viewer","text":"View images from URL. To start, add a new column to your table. This will be where you add the URL for your image. Then, add a new custom widget to the page. Choose the data table that contains the image URL and \u2018Select By\u2019 that same table. To configure, select \u2018Image Viewer\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Image URL\u2019, select the column that contains the URLs for your images. To copy the URL for an image, right click on the photo then \u2018Copy image address\u2019. This copies the URL to your clipboard. Paste this URL into your specified column in Grist. Additionally, you can add multiple images for a specific record by adding multiple image URLs, separated by a space or new line, into a single cell. Please note that a comma will not work to separate the links. When multiple image URLs are present, the image viewer custom widget will function as a carousel. Click the arrows to view additional images. For an example of the Image Viewer widget, check out our U.S. National Park Database , and add a park review while you\u2019re there! You can also check out our Meme Generator template for another great example. For a video walkthrough, be sure to watch our Custom Widgets Webinar !","title":"Image Viewer"},{"location":"widget-custom/#jupyterlite-notebook","text":"This widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. You can use the full custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. You\u2019ll be presented with a notebook where you can enter and run Python code, e.g: After typing code in a cell, click the play button or press Shift+Enter to run that cell. Unlike formulas, code isn\u2019t saved automatically. You must press the usual \u2018Save\u2019 button above the widget (outside the notebook) to persist the code within your Grist document. On the other hand, changes to settings within the notebook (e.g. keyboard shortcuts) are saved in your browser\u2019s local storage, so they\u2019re not shared with other users of the document. A special object called grist is automatically available to use in Python code, which mirrors many common methods of the usual JS plugin API . Note that many of these methods are asynchronous, so you should use await before calling them. async fetch_selected_table() : returns the data of the table backing the notebook widget. async fetch_selected_record(row_id=None) : returns a record of the table backing the notebook widget. If row_id is specified, returns the record at that row. Otherwise, returns the record at the current cursor position in a widget linked to the notebook widget. async fetch_table(table_id) : returns the data of the specified table. Note that this differs from fetch_selected_table (even for the same table) in several ways: The widget must have full document access. All columns are included, whereas fetch_selected_table excludes columns that are hidden in the widget configuration. All rows are included, whereas fetch_selected_table takes widget filters and \u2018SELECT BY\u2019 into account. The data is not sorted according to the widget\u2019s configuration. The data is fetched from the server, so the method may be slower. The values for reference columns are row IDs of the referenced table, whereas fetch_selected_table returns the values displayed based on the \u2018SHOW COLUMN\u2019 configuration. on_record(callback) : registers a callback function to run when the cursor moves in a widget linked to the notebook widget, i.e. the widget chosen from the \u201cSELECT BY\u201d dropdown in the Data section of the widget configuration. The callback function will be passed the record at the current cursor position. You can also use this as a decorator, i.e. @grist.on_record . on_records(callback) : similar to on_record , but runs when the source data of the widget changes. The callback function will be passed the same data as returned by fetch_selected_table . get_table(table_id) : returns a TableOperations class similar to the interface in the usual JS plugin API for performing CRUD-style operations on a table. See the plugin API documentation for details on the parameters. The class has the following methods: async create(records, parse_strings=True) async update(records, parse_strings=True) async upsert(records, parse_strings=True, add=True, update=True, on_many=\"first\", allow_empty_require=False) async destroy(row_ids) You can also use grist.raw for direct access to the plugin API, e.g. await grist.raw.docApi.fetchTable(table_id) . This may return raw cell values which you can decode with grist.decode_cell_value(value) . You can use many (but not all) third-party libraries in your notebook such as pandas . Many will be installed automatically when they\u2019re imported. Others will require running %pip install in a cell, e.g. %pip install pandas . Note that it\u2019s %pip and not !pip as in a regular Jupyter notebook.","title":"JupyterLite Notebook"},{"location":"widget-custom/#map","text":"The custom map widget allows you to display locations using latitude and longitude coordinates. If your data is an address, rather than in lat-long format, Grist can convert the address into lat-long coordinates. If using existing lat-long coordinates, you will need three columns; Name, Longitude and Latitude. If using an address, you will need six columns; Name, Address, Geocode, Longitude, Latitude, and Geocoded Address. Geocode is a toggle type column that should be set to true for any record you wish to convert from address to lat-long coordinates to be shown on the map. If you wish to convert all records, you can make Geocode a formula column with the formula = True . This will mark all records as True. Next, add a new custom widget to the page. Choose the data table that contains the addresses or lat-long coordinates and \u2018Select By\u2019 that same table. To configure, select \u2018Map\u2019 from the Custom dropdown. If you already have lat-long coordinates , you can set your access level to Read selected table . If you are using an address and that needs to be converted into lat-long coordinates, you will need to set your access level to Full document access because the widget needs permission to write to your document in order to add lat-long coordinates. Map all required columns. Note that Name, Longitude and Latitude are labeled as required. Geocode, Address and Geocoded Address are listed as optional. If you are using addresses and need Grist to convert these to lat-long coordinates, you must map all six columns. After mapping the necessary columns and selecting the appropriate Access Level, the map widget will populate. You can configure the map to show only the selected location by clicking the \u2018Open Configuration\u2019 option in the creator panel . Then, uncheck \u2018All Locations\u2019. Click the green check mark at the top of the widget to save the updated configuration settings. Check out our Mapping Locations template or our Crowdsourced List for two great examples! For a video walkthrough, check out our Custom Widgets Webinar .","title":"Map"},{"location":"widget-custom/#markdown","text":"The Markdown custom widget allows you to format text using Markdown while displaying the formatted text in an editable widget. For other text-editing widgets, check out our HTML and Notepad custom widgets. To start, add a new column to your table. This will be where you will add your text that will be formatted using Markdown. Then, add a new custom widget to the page. Choose the data table that contains the text formatted with Markdown and \u2018Select By\u2019 that same table. To configure, select \u2018Markdown\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the text column that contains Markdown formatting. Any Markdown formatting in the specified text column will apply and be viewable and editable in the custom widget. To edit the text directly in the widget, click the edit icon. The text will revert to display Markdown syntax that can be edited directly in the widget. When in edit mode, the edit icon will be replaced with the save icon. Be sure to click the save icon to save any changes and return to viewing the formatted text. For help on Markdown formatting, check out the Markdown Guide for basic syntax. This guide is also accessible in the Markdown widget by clicking the information icon at the top of the widget. The guide will open in a new tab of your browser for easy reference. You can find an example of the Markdown editor in our Webinar 7 (Custom Widgets) template and check out this video walkthrough from our Custom Widgets Webinar .","title":"Markdown"},{"location":"widget-custom/#notepad","text":"The Notepad custom widget allows you to format text using a rich text editor. For other text-editing widgets, check out our HTML and Markdown custom widgets. To start, add a new column to your table. This will be where details for our formatted text will be stored. Then, add a new custom widget to the page. Choose the data table that contains the column we just added and \u2018Select By\u2019 that same table. To configure, select \u2018Notepad\u2019 from the Custom dropdown and allow Full document access . Because the widget is also an editor, it needs permission to write to the document. Under \u2018Content\u2019, select the column created to store our formatted text. If the text column you chose under Content has existing text, that text will appear in the Notepad widget, ready to be formatted. Use any of the options shown here to format your text. As you can see in the screenshot below, the code for the formatted text is not useful to see in your table. You will edit text directly in the Notepad widget so you can hide this column from your data table. Check out our U.S. National Park Database or our \ud83d\uded2 Grocery List + Meal Planner for two great Notepad examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Notepad"},{"location":"widget-custom/#print-labels","text":"The Print Labels custom widget allows you to customize and print labels directly from Grist. To start, add a new column to your table. This column will contain the text for the label. Optionally, you can add a second column to specify a label count, allowing you to print more than one of the same label without having to create duplicate records. Next, add a new custom widget to the page. Choose the data table that contains the label details. To configure, select \u2018Print Labels\u2019 from the Custom dropdown and allow access to read the selected table. Under \u2018Label\u2019, select the column that contains the text to include on the labels. If you wish to print more than one of any labels, select the column that contains the number of labels for each record you wish to print. You can select from standard sheet sizes under the dropdown in the upper left of the widget. Be sure to save any changes by clicking the green check mark at the upper right of the widget. To leave any blank labels at the beginning of the sheet, click the settings icon then specify how many labels should be left blank. This is especially helpful if a portion of your label sheet has already been used. You can skip the used labels and begin printing on your first unused label. Check out our Print Mailing Labels template and our Treasure Hunt template for two great examples! You can also check out this video walkthrough from our Custom Widgets Webinar .","title":"Print Labels"},{"location":"linking-widgets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Linking Page Widgets # One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns . Types of linking # Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported. Same-record linking # Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial. Filter linking # As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next Indirect linking # Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department. Multiple reference columns # When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from: Linking summary tables # When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data. Changing link settings # After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Linking widgets"},{"location":"linking-widgets/#linking-page-widgets","text":"One big reason for placing more than one widget on a page is that widgets can be linked. When linked, selecting a record in one widget will cause another widget to update and show only the data related to the selected record. For example, let\u2019s say you have a table of Departments in a company, and a table of Employees , with each employee tied to some department. You can have one Table widget listing departments and serving as a selector for a second Table widget listing employees: Previous Next To create this, first create a page with a Table widget for Departments data, as described in Page widgets . Then in the \u201cAdd New\u201d menu, select the \u201cAdd Widget to Page\u201d option to add another Table widget for Employees data. In the widget picker, use the \u201cSelect By\u201d dropdown and choose the \u201cDEPARTMENTS\u201d widget added in the first step. That\u2019s all it takes: now selecting a department in the first table will cause the second table to show only the employees in that department. Note that this relies on the Employees table having a column of type Reference with the target table of Departments . See Reference columns .","title":"Linking Page Widgets"},{"location":"linking-widgets/#types-of-linking","text":"Linking widgets only works when there is a relationship between records in the underlying data. There are several kinds of relationships supported.","title":""},{"location":"linking-widgets/#same-record-linking","text":"Most directly you can link two widgets which show data from the same underlying table, usually linking a Table to a Card. This allows you to select a record in the Table widget and see more details of that record in the linked Card widget. For example, you can add a Card of Employees data and link it to an existing Table widget \u201cEMPLOYEES\u201d: When you select a record in the table, the new \u201cEMPLOYEES Card\u201d widget shows a card for the selected record. For another example of such linking, see the \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d and the Customizing Layout section in the associated tutorial.","title":"Same-record linking"},{"location":"linking-widgets/#filter-linking","text":"As in the Employee-Department example, when one table has a reference to another (i.e. a column of type \u201cReference\u201d), the second table can serve as a selector for the first. Essentially, selecting a record can automatically filter another widget to show only those records that refer to the selected record. In the example shown earlier, the Employees table has a \u201cReference\u201d column pointing to the Departments table, so a list of departments can serve as a selector for employees. When a department is selected, only the employees in that department will be shown. The \u201cLightweight CRM\u201d document in \u201cExamples & Templates\u201d provides another example, where selecting a contact shows only the conversations with that contact. It\u2019s also described in the tutorial . When the target of linking is a Chart widget, you get dynamic charts that reflect data associated with the selected record. For example, you could link a pie chart to a department to show the sum of salaries for each job position in the selected department. Previous Next","title":"Filter linking"},{"location":"linking-widgets/#indirect-linking","text":"Whenever a table A has a reference to B , A can be used in place of B as the source of linking. Instead of considering the currently selected record in A , linking will consider the referenced record in B as the selection. For instance, a Table widget showing Employees can serve as a selector for a Card widget showing data from Departments , using the fact that an employee record contains a \u201cDepartment\u201d reference. In the widget picker, if you select data from Departments , you\u2019ll see a \u201cSelect By\u201d option \u201cEMPLOYEES \u2022 Department\u201d: When you select an employee, you\u2019ll see the details of that employee\u2019s department.","title":"Indirect linking"},{"location":"linking-widgets/#multiple-reference-columns","text":"When a table that\u2019s the target of linking has multiple reference columns, you may need to choose which one to use for linking. For instance, a Flight record might have fields \u2018DepartureAirport\u2019 and \u2018ArrivalAirport\u2019, each of which is a Reference to the table Airports . When you select an airport in a table, you can have a choice whether to show all flights departing from this airport or all flights arriving to it. The \u201cSelect By\u201d widget will show both options to choose from:","title":"Multiple reference columns"},{"location":"linking-widgets/#linking-summary-tables","text":"When widgets display summarized data, as described in Summary tables , they can also be linked to the underlying data, data that references the underlying data, and to other summary tables. For example, you can summarize the table Employees by job position, and include the count of employees for each position, the average salary, or other summary data. You can also link the Employees table to it, so that selecting a position shows all employees in that position. Furthermore, you can link another summary table. For instance, you can summarize employees by position and gender, and link that to the summary by position. When selecting a job position, you can then see a summary by gender for that position. This is also convenient with charts. In this example, you see a pie chart with the average salary for men vs women for the selected job position. As you click different positions, the pie chart updates to reflect the selected one. More examples of such linking can be found in the Analyze and visualize tutorial. Lastly, tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. In the image below, the Champion Dog\u2019s table has a reference column to the Breeder table. The Breeder table is being summarized in the top right widget by the \u201cCountry\u201d column. Because Champion Dog references Breeder, you may add a widget of Champion Dogs that selects by a summary table of Breeder data.","title":"Linking summary tables"},{"location":"linking-widgets/#changing-link-settings","text":"After a widget is added, you can view and change its link settings from the right panel. One way to get to it is to click on the three-dots icon on the top right of the widget, and click on the \u201cData selection\u201d menu option: This opens the side panel, which shows what data is shown, and which widget, if any, serves as its selector. You can change the \u201cSelect By\u201d setting here, or click the green \u201cEdit Data Selection\u201d button, and change it in the widget picker dialog.","title":"Changing link settings"},{"location":"custom-layouts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Custom Layouts # You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location. Layout recommendations # While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts. Layout: List and detail # The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest. Layout: Spreadsheet plus # Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact. Layout: Summary and details # Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month. Layout: Charts dashboard # If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Custom layouts"},{"location":"custom-layouts/#custom-layouts","text":"You can easily add multiple widgets to one page, as described in Page widgets , and link them as described in Linking widgets . It is also easy to customize their arrangement. Move the mouse cursor over the title of the widget. A small drag icon will appear over to the left of the title. When you press this icon, you can drag the entire widget to a different place on the screen. As you move it close to the edge of the screen, or the edge of another widget, you\u2019ll see a dashed outline \u2013 sometimes more than one \u2013 where the widget can be dropped. Release the mouse to reposition this widget. To resize widgets, move the mouse cursor between two widgets until a dashed line appear. Drag this dashed line to resize. To expand a widget, click the expand icon at the upper-right of the widget. This will open a full-page view of the widget. To collapse a widget, click and drag a widget to the widget tray at the top of the page. When you click on a collapsed widget, it opens in a full-page view. To restore it to the main page, just drag the collapsed widget to the desired location.","title":"Custom Layouts"},{"location":"custom-layouts/#layout-recommendations","text":"While there is no limit to how complicated a layout you can create, you should aim for simple layouts that will be easy to use for your users (even when you are the only user!) One rule of thumb is that a widget controlled by another \u201cselector\u201d widget (see Linking widgets ) should be to the right or below it. Here are some common layouts.","title":"Layout recommendations"},{"location":"custom-layouts/#layout-list-and-detail","text":"The most common one is to have a list of items on the left, with one or more widgets on the right providing more information. For instance, the Lightweight CRM example includes a list of people on the left, with a person\u2019s card and a table of related interactions to the right of the list. In this usage, you might want to include in the list only the minimal information you need, perhaps only a contact\u2019s name. If your table has many columns, a quick way to leave only a few is via the widget options in the right-side panel. In the table widget, click the three-dot menu on the top right, and select \u201cWidget options\u201d. You\u2019ll see a list of \u201cVisible Columns\u201d. Click \u201cSelect all\u201d link on top of that list: Now uncheck the few fields you want to keep, and click \u201cHide columns\u201d to hide the rest.","title":"Layout: List and detail"},{"location":"custom-layouts/#layout-spreadsheet-plus","text":"Sometimes a wide spreadsheet with many columns is convenient. If you\u2019d like to see more info associated with the rows of this spreadsheet, you can add widgets below it. These could be details linked to the spreadsheet, or summary tables that show totals or other global info. For instance, here is a possible layout based on the Lightweight CRM example. It shows contacts as a wide spreadsheet, and below that includes sections with an overall summary, and interactions for the selected contact.","title":"Layout: Spreadsheet plus"},{"location":"custom-layouts/#layout-summary-and-details","text":"Sometimes it\u2019s useful to divide up a large dataset into subsets. For instance, you might have credit card transactions, and want a way to view them one month at a time. To do it, you\u2019ll use a \u201cMonth\u201d column, creating one with a formula if needed. Then create a summary table grouped by \u201cMonth\u201d (see Summary tables , and link the table of transactions to it. We can then select a month and see a spreadsheet of only the transactions in that month.","title":"Layout: Summary and details"},{"location":"custom-layouts/#layout-charts-dashboard","text":"If you have many charts, you can just lay them out in a grid to create a top-level dashboard. For dynamic charts in which data is selected by another table, a layout like List-and-detail above would work well. One tip is to include both a Table widget and a Chart widget, configured and linked the same way, and differing only in the widget type: Having a table alongside the chart can be a useful reference, as well as provide more context to what is visible in the chart.","title":"Layout: Charts dashboard"},{"location":"record-cards/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Record Cards # Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record. Editing a Record Card\u2019s Layout # You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts . Disabling a Record Card # You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Record cards"},{"location":"record-cards/#record-cards","text":"Record Cards are a quick, easy way to view a record\u2019s details. To view a record\u2019s details as a card, hover over the row number and click the arrow icon that appears. Select \u2018View as card\u2019 from the dropdown menu. This opens an editable card view of the record\u2019s details. If your table contains a reference or reference list column , you can click the link icon to open the linked record\u2019s card. A record card will open to display record data for the referenced record.","title":"Record Cards"},{"location":"record-cards/#editing-a-record-cards-layout","text":"You can edit a record card\u2019s layout from the Raw Data page. This new layout will apply to any new card widgets using the same table as source data. Click the card icon to open. You can drag and drop fields to rearrange, resize and add/delete fields from the view. Learn more about editing card layouts .","title":"Editing a Record Card’s Layout"},{"location":"record-cards/#disabling-a-record-card","text":"You can also disable a record card from the Raw Data page. To disable a record card for a particular table, click the three-dot icon to the right of the table name then select \u2018Disable Record Card\u2019 from the dropdown.","title":"Disabling a Record Card"},{"location":"summary-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables # Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics. Adding summaries # Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs. Summary formulas # When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group . Changing summary columns # The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table. Linking summary tables # You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets . Charting summarized data # Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables. Detaching summary tables # Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Summary tables"},{"location":"summary-tables/#summary-tables","text":"Summary tables are the workhorse of data analysis in Grist. They are similar to \u201cpivot tables\u201d in spreadsheets, and to GROUP BY clauses in databases. They let you get table-wide totals or any subtotals. Summary tables have many uses: Group projects by status, or by priority. Summarize bank transactions by year, or quarter, or by category. Group employees by position, or gender, or department. Summarize by any combination, such as position and gender and department. Group all records to calculate table-wide totals and statistics.","title":"Summary Tables"},{"location":"summary-tables/#adding-summaries","text":"Click \u201cAdd New\u201d button and select \u201cAdd Page\u201d or \u201cAdd Widget to Page\u201d. In the widget picker, click the summation icon ( ) next to the table you\u2019d like to summarize. When creating a summary table, you choose a field or a combination of fields by which data should be grouped. These are called the \u201cGroup by\u201d fields. The summary table will contain one row for each group-by value. For instance, if \u201cPosition\u201d is selected as a group-by field, the summary table will contain one row for each job position. Grist\u2019s powerful formulas then allow arbitrary calculations on the matching subsets of the underlying data. Keep in mind that the group-by fields determine which groups should exist in the summary table. Do not select values you\u2019d like to calculate (e.g. for average salary) as group-by fields; these will be created using formulas in the next step. Tip: You can think of rows in a summary table as buckets into which your records will be placed. Group-by fields determine the labels for these buckets. For instance, a summary of projects grouped by status (e.g. \u201cActive\u201d, \u201cCompleted\u201d, \u201cNotStarted\u201d) will have three such buckets, one for each status. Every project goes into one of these three buckets. You can then easily calculate info for each folder, such as a count of projects or the total of their costs.","title":"Adding summaries"},{"location":"summary-tables/#summary-formulas","text":"When you add a summary table, each of the selected group-by fields becomes a column in the new table. Everything else in the summary table is calculated using formula columns. Some of these columns are created automatically, as a convenience. Specifically, a column count will be added to show the number of records in the group represented by the current summary row. And for any numerical column in the original data, the summary table will contain a same-named column with the total. For instance, the summary of Employees grouped by Position would look like this: Tip: A summary widget will have a header like \u201cEMPLOYEES [by Position]\u201d, to indicate that it\u2019s showing summary data for the Employees table, grouped by \u201cPosition\u201d. You can click the title to rename it. If you select a cell in a column like count or AnnualPay and hit Enter , you\u2019ll see the formulas that calculate them: count is len($group) AnnualPay is SUM($group.AnnualPay) The mysterious $group is simply another column, hidden by default (but you may unhide it). It contains for each cell the group of records represented by this summary row. Side note for Python fans. $group is a special Python object. It\u2019s an iterable collection of records. Using an attribute like $group.A is a shorthand for the list of values in the A column of all the records in the group, similar to [r.A for r in $group] . If you unhide this column, it will show as a python list of numeric row IDs which identify the records in the group. Sometimes the automatically created columns don\u2019t make sense. For instance, the sum of PayRate isn\u2019t very meaningful. Simply delete such columns, either using the column menu or using the Alt + Minus keyboard shortcut. You are free to change formulas for the automatically created columns, or to add new formula columns. Note that summary tables do not allow adding non -formula columns. For instance, you might want to change the formula for PayRate from SUM($group.PayRate) to AVERAGE($group.PayRate) , which would be a more interesting value. Here are some useful recipes of formulas in summary tables: Average : AVERAGE($group.PayRate) or SUM($group.PayRate) / $count Standard deviation : STDEV($group.PayRate) Maximum or minimum : MAX($group.PayRate) , MIN($group.PayRate) Sum over a subset of records : SUM(r.AnnualPay for r in $group if r.EmploymentStatus == \"Active\") Weighted average : AVERAGE_WEIGHTED(zip($group.Life_Expectancy, $group.Population)) In fact, you may use the full power of Python to calculate what you\u2019d like. As for any table, your formula may refer to any of the columns in the summary table, not only $group .","title":"Summary formulas"},{"location":"summary-tables/#changing-summary-columns","text":"The group-by columns in a summary table are created when you add the widget. It\u2019s not possible to modify the values in them, or to modify their settings, such as type. The settings and values of the group-by columns reflect those in the underlying table. When new values appear in the underlying table for the group-by columns, the summary tables will also get new rows automatically. What you may change is which columns the table is grouped by. Click the three dots on the top right of the summary table, and click \u201cData selection\u201d: The settings in the side pane tell you which data is shown and how it\u2019s grouped: You can click \u201cEdit Data Selection\u201d button to open the same widget picker that you used to add the summary table. You can now deselect some \u201cGroup by\u201d fields and select others, and click \u201cSave\u201d to update the summary table.","title":"Changing summary columns"},{"location":"summary-tables/#linking-summary-tables","text":"You can link summary tables to other widgets. If you have a summary table on a page, it can be used as a selector for a table of underlying data, or for a more detailed summary. For example, if you have a summary of Employees by \u201cPosition\u201d, it will be available as a \u201cSELECT BY\u201d option when adding an unsummarized table of Employees : The result is that you can select a position in the summary table, and see all employees in that position. You could similarly link a summary of Employees grouped by \u201cPosition\u201d and \u201cGender\u201d to the first widget: You can then select a position in the \u201cEmployees [by Position]\u201d widget, and see a breakdown by gender among the employees in that position. Note that such linking requires the new widget to include both \u201cPosition\u201d and \u201cGender\u201d in its group-by columns. For more about linking, see Linking widgets .","title":"Linking summary tables"},{"location":"summary-tables/#charting-summarized-data","text":"Summary tables are a great source of data for charts, including dynamic charts. In the example above, we could add another widget showing Employees grouped by \u201cPosition\u201d and \u201cGender\u201d, but this time in a Chart widget. Select \u201cBar Chart\u201d for the chart type, and select \u201cGender\u201d and \u201cAnnualPay\u201d as Visible Series. You can now click on a position, and see visually the gender difference in average salary for this position. The Analyze and visualize tutorial shows other examples of charts based on summary tables.","title":"Charting summarized data"},{"location":"summary-tables/#detaching-summary-tables","text":"Summary tables are computed from underlying data. Sometimes, however, its useful to \u201cdetach\u201d a summary table and turn it into an independent data table. For example, we\u2019ve seen how to summarize a table of Employees , grouping it by the column \u201cPosition\u201d. Let\u2019s say you want to associate some data with each position, such as a job description, or a workers compensation insurance code. Such data belongs in its own table, with one row for each position, and a few columns. Summary tables provide an easy way to create such a table. Add a page with a summary of Employees grouped by \u201cPosition\u201d. Now, in the right panel\u2019s \u201cData\u201d tab, click the \u201cDetach\u201d button. Your summary table just got turned into a brand new table with the same rows and an auto-generated name, like \u201cTable1\u201d. You can rename it to \u201cPositions\u201d, and add the columns you need: The calculated columns remain. In fact, anything you could calculate about each position in the \u201cEmployees [by Position]\u201d summary table, you may still calculate in the new \u201cPositions\u201d table. One difference is that new values will not get added to the detached table automatically. In other words, if a never-before-seen position (perhaps \u201cChief Troublemaker\u201d) appears in the underlying data, a summary table would update automatically to include it, but a detached table will not.","title":"Detaching summary tables"},{"location":"on-demand-tables/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . On-Demand Tables # On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API. Make an On-Demand Table # To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment. Formulas, References and On-Demand Tables # In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"On-demand tables"},{"location":"on-demand-tables/#on-demand-tables","text":"On-demand tables are an experimental feature The design of on-demand tables may change. For example, configuration options may be added, or aspects of the behavior of on-demand tables may be changed entirely. A defining feature of spreadsheets is the ability to update cells instantly as data they depend on changes. But sometimes a table is just a store of data, without any fancy calculations needed. In that case, you can choose to mark the table as \u201cOn-Demand\u201d in Grist. Grist can then serve data from that table faster, and make certain optimizations that may be helpful as the table gets large and regular tables become slow. When a table is marked \u201cOn-Demand\u201d: Data in the table will not generally be available for use in formulas. The table remains available for viewing and editing, but with caveats. Here\u2019s what you need to know about viewing data: Viewing is limited to 10000 rows at a time. Subsets of the table\u2019s rows can be selected by linking widgets , as for regular tables. You can expect good performance of linked widgets when the subset of the table\u2019s rows is less than 10000 rows, even if the full table is much larger. Here\u2019s what you need to know about editing data: You can edit data as normal in an On-Demand table. Automatic updates of anything that depends on that data simply won\u2019t happen. After edits, you need to reload the webpage to see everything updated. Here are some reasons you might make a table On-Demand: You want to make summaries and charts of slices of a large dataset using linked widgets . You are storing a lot of data in the table, and all you need to do with it is read parts of it back out via the API.","title":"On-Demand Tables"},{"location":"on-demand-tables/#make-an-on-demand-table","text":"To convert a table to be an \u201cOn Demand\u201d table, open the right panel, pick the \u201cTable\u201d panel, and the \u201cData\u201d section. Click on \u201cAdvanced Settings\u201d and you should see a \u201cMake On-Demand\u201d button. If you change your mind, and don\u2019t want the table to be \u201cOn-Demand\u201d anymore, you can find a button to undo this setting in the same place: Changing a table to become \u201cOn-Demand\u201d or to stop being \u201cOn-Demand\u201d will force the document to reload for all users viewing it at that moment.","title":"Make an On-Demand Table"},{"location":"on-demand-tables/#formulas-references-and-on-demand-tables","text":"In general, formulas and on-demand tables don\u2019t go together. That said, if you\u2019re careful you can use the following very simple formulas: $column - where the column mentioned is not itself a formula. This copies data from another column verbatim. $reference.column - where reference is a reference column , and column is not itself a formula. This formula support is enough to unlock Grist\u2019s linking widgets feature, which is why it is present. In general, if you try using formulas and On-Demand tables, you are setting yourself up for sadness. Remember, like any edit of an On-Demand table, when you add or change a formula column you\u2019ll generally need to reload to see cell values updated. Some type conversions, such as converting a column to be a reference, are not effective for On-Demand tables. So it is important to perform such conversions before making a table On-Demand. From formulas in regular tables, you cannot access the content of on-demand tables.","title":"Formulas, References and On-Demand Tables"},{"location":"col-types/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Columns and data types # Adding and removing columns # Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID Reordering columns # To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here. Renaming columns # You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID. Formatting columns # Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting . Specifying a type # Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error): Supported types # Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images. Text columns # You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links. Markdown # Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML. Hyperlinks (deprecated) # When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\" Numeric columns # This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats. Integer columns # This is strictly for whole numbers. It has the same options as the numeric type. Toggle columns # This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type . Date columns # This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference . DateTime columns # This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings . Choice columns # This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step. Choice List columns # This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists . Reference columns # This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Reference List columns # Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details. Attachment columns # This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Columns & types"},{"location":"col-types/#columns-and-data-types","text":"","title":"Columns and data types"},{"location":"col-types/#adding-and-removing-columns","text":"Every Grist table, when first created, has three columns called A, B, and C. To rename a column, hover on the column header, click on the drop-down, then select \u201cRename column\u201d (you can also just click on the column header twice). To delete a column, hover on the column header, click on the drop-down, then select \u201cDelete column\u201d. To add a column, click on the \u201c+\u201d symbol in the header row to open the \u201cAdd Column\u201d menu. The first option in the menu, \u201cAdd Column\u201d, will add a new, empty data column to your table. If you know what column type you need, the next option allows you to assign a type for your new column. Select a column type from the expanded menu. The third option, \u201cAdd formula column\u201d, will add a new formula column with the formula entry box immediately opened so you can begin entering your formula without additional clicks. \u201cHidden Columns\u201d expands to show a list of columns hidden from this view that can be quickly added back. \u201cLookups\u201d allows you to add data columns from related tables. You can use reference columns to relate data in different tables. Learn more about References and Lookups . \u201cShortcuts\u201d lists the most frequently used trigger formula functions. Learn more about each shortcut option at the links below: Timestamp Authorship Detect Duplicates in\u2026 UUID","title":"Adding and removing columns"},{"location":"col-types/#reordering-columns","text":"To reorder a column, first select the column if it isn\u2019t already selected, by clicking on the column header. Next, click and hold on the column header. After a second or two, you\u2019ll be able to drag the entire column to its new location. Another way to reorder columns is via the widget options: In the visible columns section, the columns can be dragged around freely to reorder them. You can also hide columns here.","title":"Reordering columns"},{"location":"col-types/#renaming-columns","text":"You can rename columns in several ways. One way is to double click a column header. Then, you can rename the column or add a column description. You can also hover on the column header, click on the drop-down, then select \u201cRename Column\u201d. This opens the same pop up seen above. Selecting \u201cColumn Options\u201d in the same drop-down opens the creator panel. From here, you can edit the Column Label, shown at the top or add a description. A bonus with this method is that you can also control the identifier given to the column in formulas. By default this is based on the field name, with any characters Python doesn\u2019t like replaced with \u201c_\u201c, and a number added if needed to keep the name unique within your table. If you don\u2019t like this identifier, you can change it, though it will still need to be Python-friendly. Click the link icon to make the ID field editable, then enter the new Column ID.","title":"Renaming columns"},{"location":"col-types/#formatting-columns","text":"Header and cell styles can be modified under the Column tab of the creator panel. When you open the styling menu, you have the option to apply text formatting as well as text and fill colors. If you want more color options, click the fill color box then find the shade you want. If you have a specific color you wish to use, you can enter the hex code, RGB or HSL values. Cell style can also be changed based on conditional rules. Learn more about Conditional Formatting .","title":"Formatting columns"},{"location":"col-types/#specifying-a-type","text":"Grist columns have types, similar to other spreadsheets or databases. The type of a column controls its appearance and the help Grist will offer you when editing cells. When you create a new column, it initially has the Any type. When you enter the column\u2019s first cell, Grist tries to narrow this type. If you enter a number, the column will be changed to Numeric type, which is right-aligned by default. If you enter something that doesn\u2019t look like a number, the column will be changed to Text type, which is left-aligned by default. To inspect the type of a column, hover over the column header, then click on the drop-down, then select \u201cColumn Options\u201d. The \u201cColumn Type\u201d section is what you are looking for. You will often want to control the column type manually. You can change it in the \u201cColumn Type\u201d section. For example, here we set a column full of \u201cyes\u201d and \u201cno\u201d responses to be of type Toggle : One advantage of doing so is that Grist can now offer you ways to visualize the column that are specialized to on/off style values. Each column type has different options in the \u201cCell Format\u201d section of the side panel: Regardless of the column type, you can enter any value in cells. If a value entered is incompatible with the defined type, the cell will be highlighted with an error (and columns referencing the invalid value will also display an error):","title":"Specifying a type"},{"location":"col-types/#supported-types","text":"Grist supports the following types: Type Description Text ( Default ) Any string of text. Numeric Floating point numbers. Integer Integers (whole numbers). Toggle Boolean (True / False) Date Valid date (without a time component). DateTime Valid date + time. Choice Single value from a list of pre-defined valid values. Choice List Multiple values from a list of pre-defined valid values. Reference A reference column to another table. Reference List A list of references to another table. Attachment Cells where you can place files or images.","title":"Supported types"},{"location":"col-types/#text-columns","text":"You can put any text you like in this type of column. You can control alignment and word-wrap. Text columns support multi-line text: you can add newlines by pressing Shift + Enter . As for most column types, you can also set default cell formatting for text columns, including color, background color, bold, or italics. You can enable rich text formatting by selecting \u201cMarkdown\u201d as the cell format instead of the default plain text. Markdown also replaces the older \u201cHyperLink\u201d cell format for prettier web links.","title":"Text columns"},{"location":"col-types/#markdown","text":"Markdown formatting allows you to add rich text elements like headings, links, or lists, as well as bold or italics formatting directly within a cell. Cells with Markdown are still stored as plain text, but certain elements in the text are interpreted as formatting. For example, **bold** is shown as bold , and [my link](https://getgrist.com) is shown as my link . Here is a screenshot of a cell being edited and showing raw markdown, while the cell below it shows this same markdown when it is rendered: Markdown is documented online, such as in this handy markdown cheatsheet , or this documentation of markdown syntax . The Markdown cell format in Grist supports a subset of markdown, including lists, links, headings, bold and italic text, code spans and code blocks, and blockquotes. It does not currently support images or custom HTML.","title":"Markdown"},{"location":"col-types/#hyperlinks-deprecated","text":"When a Text column uses \u201cHyperLink\u201d formatting, values get formatted like so: https://getgrist.com will show https://getgrist.com . Grist Labs https://getgrist.com will show Grist Labs (linking to \u201chttps://getgrist.com\u201d with \u201cGrist Labs\u201d as the text). Email Help mailto:support@getgrist.com will show Email Help , a link which would open an email program to compose an email to support@getgrist.com. In general, the value until the last space is used as the link text, while the last word is used as the link destination. Link formatting is particularly useful when links are generated using a formula such as: $Company + \" \" + $Website Note that the same functionality is now possible using the Markdown cell format, which offers a more standard option for creating links with custom text, making the Hyperlink cell format obsolete. For example, using the Markdown cell format, [Grist Labs](https://getgrist.com) will show Grist Labs . You can also generate markdown links using a formula like: f\"[{$Company}]({$Website})\"","title":"Hyperlinks (deprecated)"},{"location":"col-types/#numeric-columns","text":"This type is for numbers, including floating-point numbers. In addition to controlling alignment and color, you can choose the number format, and the minimum and maximum number of digits to show after the decimal point. Choosing the \u201cSpinner\u201d option for CELL FORMAT will show arrows in each cell for increasing/decreasing the number. The options under NUMBER FORMAT include: $ : Format for currency amounts, such as dollars or euro. Selecting the $ will add a currency prefix, thousands separators, and default to 2 digits after the decimal point. It will also open a currency selector for international currencies. Setting Default Currency You can set a document\u2019s default timezone, locale, and currency in Document Settings . , : Turn on the display of thousands separators. % : Show numbers as percentages. E.g. \u201c0.5\u201d would show as \u201c50%\u201d. Exp : Show numbers in exponential (or scientific) notation. E.g. \u201c1234\u201d would be shown as \u201c1.234E3\u201d. (-) : Show negative numbers in parentheses, without a leading minus sign. This is commonly used in accounting, and usually combined with $ or , formats.","title":"Numeric columns"},{"location":"col-types/#integer-columns","text":"This is strictly for whole numbers. It has the same options as the numeric type.","title":"Integer columns"},{"location":"col-types/#toggle-columns","text":"This type is for storing true/false values. The values can be shown as text, checkboxes, or switches. See also example in Specifying a type .","title":"Toggle columns"},{"location":"col-types/#date-columns","text":"This type is for storing calendar dates (without a time of day component). More details in Working with dates . You can choose the format for dates, see the date formatting reference .","title":"Date columns"},{"location":"col-types/#datetime-columns","text":"This type is for storing calendar dates plus time of day. More details in Working with dates . You can choose the format for dates, see the date and time formatting reference . You can also specify the timezone to display for. If you\u2019d like to set a default timezone for your document, you can do so in Document Settings .","title":"DateTime columns"},{"location":"col-types/#choice-columns","text":"This type is for storing one of a set of valid values, where you get to specify the available values. There\u2019s an example of using this type of column in the Lightweight CRM example . If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. You can add or remove choices by either clicking Edit or on the Choices box. To add a choice, type its value in the text field below the other choices and press Enter . To remove a choice, click the delete icon to the right of the choice or select the choice by clicking on it, and then press Backspace / Delete . To apply/save your changes, you can either click the Save button or press Enter . To discard your changes, you can either click the Cancel button or press Escape . Clicking the color dropdown to the left of a choice will open a color picker for customizing the fill and text color of a choice. Changes to colors are reflected inside cells and throughout the rest of your document once you save your changes. Choices can be re-arranged by clicking and dragging them, which determines the order in which they appear when typing into a cell. You can also rename a choice by clicking on it and typing a new name. Renaming a choice will also rename all the values that are used in your document. The configuration editor supports many convenient keyboard shortcuts. You can press the Up Arrow and Down Arrow keys to navigate between selected choices; hold the Shift key to bulk select adjacent choices while clicking or using the arrow keys; and hold the Command / Control key to multi-select choices while clicking. To select all choices, you can press Command / Control + A . Undo and redo are also supported in the configuration editor. While your cursor has focus on the editor text field, you can press Command / Control + Z to undo your last change, and Command / Control + Shift + Z to redo it. The configuration editor also supports copy and paste. To copy, select the choices you want copied and press Command / Control + C . To paste, focus on the text field and press Command / Control + V . Choices are pasted in bulk if the clipboard contains multiple lines of text. Choices can also be copied from one column\u2019s configuration editor and pasted into another, which will copy over both the values and their configured colors. When typing into a Choice column cell, your configured choices will be shown in an autocomplete menu. You can either click on a choice, or use the arrow keys and Enter to add a choice to a cell. If your input is not one of the valid choices, Grist will display a menu option for conveniently adding it as a valid choice and into the cell in one step.","title":"Choice columns"},{"location":"col-types/#choice-list-columns","text":"This type is for storing multiple values from a set of valid values, where you get to specify the available values. If you start off with a populated text column, Grist will take all unique values from that column as the valid choices. Choice List columns are configured the same way Choice columns are, and support the same level of customization and keyboard shortcuts. They differ in the number of choices they allow to be entered in each cell. While Choice columns only allow at most one value in a cell, Choice List columns allow many. Like with Choice columns, when typing into a Choice List column cell, the valid choices will be shown in an autocomplete menu. Once you\u2019ve selected a value, you can continue adding choices to the same cell. Choices can be re-arranged inside their cells by clicking and dragging them while the cell is being edited. You can also use the arrow keys and the Delete key to navigate and delete choices, or simply click the delete icon when hovering your cursor over a choice. Filtering Choice and Choice List columns\u2019 dropdown lists When entering data into a Choice or Choice List column you will see a dropdown list of all available choices. Sometimes it would be useful to filter the dropdown list based on a condition, such as the value in another cell. Writing conditions to filter choice dropdown lists is similar to filtering reference column\u2019s dropdown lists .","title":"Choice List columns"},{"location":"col-types/#reference-columns","text":"This sets up a cross-reference to another table. You can specify the table to reference, and a column within that table to show. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference columns"},{"location":"col-types/#reference-list-columns","text":"Like Reference columns , but can store multiple references in a single cell. There\u2019s a lot you can do with this kind of column, see Reference columns for details.","title":"Reference List columns"},{"location":"col-types/#attachment-columns","text":"This column type lets you insert entire files and images in cells. When images are added in cells, a preview thumbnail is shown in the cell. The \u201cSize\u201d bar gives control of the scale of this thumbnail. When you create an attachment column, cells of that column will have a paperclip icon: When you click on a paperclip icon, you can select a file to attach. If it is an image, you\u2019ll see a thumbnail of it in the cell. If you hover over the image, you\u2019ll see a paperclip icon again, which you can use to add more files to the same cell. You\u2019ll also see an \u201copen-eye\u201d icon, which when clicked brings up a larger view of all of the cell\u2019s attachments, and gives you a way to rename them, download them, or remove them.","title":"Attachment columns"},{"location":"col-refs/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference and Reference Lists # Overview # In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values. Creating a new Reference column # Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid: Adding values to a Reference column # Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference: Creating a two-way Reference # By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other. Converting Text column to Reference # When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table: Including multiple fields from a reference # A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas. Creating a new Reference List column # So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need. Editing values in a Reference List column # To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape . Understanding reference columns # Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella . Filtering Reference choices in dropdown lists # When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Reference columns"},{"location":"col-refs/#reference-and-reference-lists","text":"","title":"Reference and Reference Lists"},{"location":"col-refs/#overview","text":"In most datasets, each record shares a relationship with some other type of data. Relational databases like Grist are built on these relationships. ID numbers are assigned to people, employees are organized into departments, receipts list different products, etc. These relationships come in four different forms: One-to-One : An exclusive relationship between two unique records, like an individual and their birth certificate, or a company and its incorporating document. Each record can only have one counterpart, and vice versa. One-to-Many : A relationship in which one record can be related to any number of corresponding records, like a department and its employees, or a person with multiple email addresses. The first record may be related to multiple other records, but each of those records can only be related to the first. Many-to-One : A relationship that is the inverse of a One-to-Many relationship. Consider the previous examples, but reversed: employees and the department they are assigned to, or multiple email addresses and the person they belong to. Many-to-Many : A relationship in which any number of records can be related to any number of corresponding records, like a roster of students and the different courses they are enrolled in. These relationships are the least restrictive, and can express the most complex relationships. Reference and Reference List columns in Grist allow one table to create an explicit reference to another. In the database world this is similar to a foreign key. In the spreadsheet world this is similar to a VLOOKUP , but much more powerful and easier to use. In this guide we\u2019ll use the term underlying table for the table that lists all available values, and referencing table for the table that uses those values.","title":"Overview"},{"location":"col-refs/#creating-a-new-reference-column","text":"Suppose we have a document with two tables, Clients and Projects . The Clients table lists our clients - names, contacts, signing dates - and the Projects table lists projects we do for clients. There are all sorts of things Grist can do for us if we let it know that the Client column in the Projects table is referring to clients listed in the Clients table. We can do this by converting the Client column to a \u201creference column\u201d. Open the creator panel on the right-hand side of the page (see Specifying a type ) and set the \u2018Column Type\u2019 to \u2018Reference\u2019. Adjust the \u2018Data from Table\u2019 option to be the correct table you want to cross-reference, and the \u2018Show Column\u2019 option to match which column of that table you\u2019d like to show. Then hit \u2018Apply\u2019 when you\u2019re happy with the result. Understanding the reference The column value always references the entire record in the underlying table. The displayed value can be any column from that record, as selected in Show Column . You can also include additional columns to display as explained later. In our example, you can see little link icons appearing in the Client column cells, showing that they have been successfully cross-referenced with the Clients table. Once the column type is set, you can start typing into it or double-click it to see a dropdown list of all available values. Note that the table Clients and the column Client are related by the column type rather than by name. They can be named anything. Spotting reference columns You can tell that the values in a column represent a reference by the link icon that appears next to the values. If you accidentally type in a value that is not present in the Clients table, its value will be highlighted as invalid:","title":"Creating a new Reference column"},{"location":"col-refs/#adding-values-to-a-reference-column","text":"Sometimes it\u2019s useful to add a new value to the dropdown list without having to switch to the underlying table. Reference columns make it easy! Just type in the value you want add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference:","title":"Adding values to a Reference column"},{"location":"col-refs/#creating-a-two-way-reference","text":"By default, new reference columns are added to a table as one-way references. This means they exist only as a single column within that specific table. Grist also allows you to configure a reference column with two-way references. This creates a new column in the referenced (or target) table, meaning that references between the two will be shown in both tables and remain synchronized. For example, we can make the existing reference column, Client , a two-way reference. Under the \u2018Column\u2019 tab of the Creator Panel, select \u2018Add two-way reference\u2019. This creates a new reference column in the referenced table, Clients . Since there can be multiple projects for a single client, the automatically-created Projects column is a Reference List . When part of a two-way reference, updates made in one column will be reflected in the other.","title":"Creating a two-way Reference"},{"location":"col-refs/#converting-text-column-to-reference","text":"When working with existing data, it\u2019s common to have existing text values that should really be reference values. Don\u2019t worry, conversion is simple! Just change the column type to \u2018Reference\u2019 and Grist will automatically find and substitute matching values for references. If some values are not found, they\u2019ll be shown as invalid. You can then either add them to the underlying table or select the proper values for them. In this example, the first three values match perfectly, but Forest Labs is invalid because it doesn\u2019t exist in the Clients table:","title":"Converting Text column to Reference"},{"location":"col-refs/#including-multiple-fields-from-a-reference","text":"A big benefit of reference columns is that they allow you to easily bring in multiple columns from the underlying table. In our example, if you wanted to bring in $Client.Contact to the Projects table, you can just select the Contact column from the \u2018Add Referenced Columns\u2019 section and it will be automatically added to the Projects table: If you\u2019re comfortable using formulas, you can see that the added column is just the formula =$Client.Contact . If you were so inclined, you could achieve the same result by manually adding the formula column. You can also use any other fields from Clients table by referencing $Client in formulas in the Projects table. Note that in formulas, we use the name of the reference column ( $Client ) to refer to a linked record, not the name of the table (which is Clients here). Don\u2019t let the similarity of the names in this example confuse you. Using references in formulas You may have noticed that the underlying table is Clients (plural) but the formula is $Client.Contact (singular). That\u2019s because the formula refers to the referencing column, not the underlying table. In our example, the referencing column is Client . Check out our article on References & Lookups to see more ways to use references in formulas.","title":"Including multiple fields from a reference"},{"location":"col-refs/#creating-a-new-reference-list-column","text":"So far our example has only dealt with projects that have a single client. Suppose that we also have projects with multiple clients, and we\u2019d like to maintain references to them all from the Client column of the Projects table. We can let Grist know that the Client column contains multiple references by changing its type to \u2018Reference List\u2019. This column type can reference multiple records, and can also be thought of as a multi-select. Open the creator panel (see Specifying a type ) and set the \u2018Column Type\u2019 of Client to \u2018Reference List\u2019. Grist will automatically convert any of your existing references to reference lists. Once you\u2019re happy with the result, just hit \u2018Apply\u2019 and the Client column will be ready to accept as many clients as your projects need.","title":"Creating a new Reference List column"},{"location":"col-refs/#editing-values-in-a-reference-list-column","text":"To make changes to a Reference List cell, simply double-click the cell or press the Enter key after you have selected the cell you want to edit. You can also start typing after selecting a cell if you\u2019d like to write over any existing contents. Doing so will open an editor like the one in the example below. Like with reference columns, the autocomplete menu will populate with suggestions as you type. If you type in a value that\u2019s not present in the referenced table, you can select the + value to add a new row to the referenced table with your value. To delete existing references, simply press the Backspace key, or move your cursor over a reference and click the X icon. You can also rearrange references in the editor by dragging them with your mouse. To save your changes and close the editor, either press Enter or Tab , or click anywhere outside the editor. To close the editor and discard any changes you\u2019ve made, press Escape .","title":"Editing values in a Reference List column"},{"location":"col-refs/#understanding-reference-columns","text":"Cells in a reference column always identify an entire record in the referenced table. For convenience, you may select which column from that record to show by setting the \u2018SHOW COLUMN\u2019. However, the cell\u2019s value is always a record\u2019s unique ID. Similarly, Reference Lists store a list of record ids. What does that really mean? Let\u2019s take a look at the Class Enrollment template. In the Classes table, the Instructor column is a reference column that references data from the Staff table. Full Name is selected under \u2018Show Column\u2019 and is used as a label to represent the record from the Staff table that is being referenced here. We can change that label to any other value contained within the record. Let\u2019s change it to \u2018Row ID\u2019. The row ID is what is actually being stored within the reference or reference list column. With this ID, we can fetch any data associated with this record. In the first row of the Classes table, we see Staff[2] as the value in the Instructor column. This represents the record in the Staff table with Row ID = 2 . We can navigate to the Staff table and see which record is assigned Row ID = 2 . To view a record\u2019s unique ID, add a new column with the formula = $id . We can see that the value in the Full Name column for the record with Row ID = 2 is Dowbakin, Daniella . If we revert back to our original settings for the Instructor column of the Classes table, where Full Name was selected under \u2018Show Column\u2019, we see that the Full Name value associated with Staff[2] is Dowbakin, Daniella .","title":"Understanding reference columns"},{"location":"col-refs/#filtering-reference-choices-in-dropdown-lists","text":"When entering data into a reference column, you will see a dropdown list of all available values to choose from. Sometimes the list can get long, and in some cases confusing. For example, say we\u2019re creating a database of stadiums and noting their locations using a dropdown to select their respective cities from a list of world cities: A dropdown list that long is impractical. Instead, it would be useful if the dropdown list of cities was filtered to only show cities based on the adjacent Stadium Country column. To do this, we\u2019ll work with three tables \u2013 Countries , Cities , and Stadiums . You can see this example here . The Countries table lists each Country as a unique record. The Cities table creates a relationship between a city and a country. Each City is its own record, assigned to its corresponding Country in the adjacent reference column. The Stadiums table is linked to both tables via reference columns: Stadium Country references the Countries table, and Stadium City references the Cities table. To filter a reference column\u2019s dropdown list \u2013 here, the Stadium City column \u2013 first select the reference column, then click \u2018Set dropdown condition\u2019 in the Creator Panel under the \u2018Column\u2019 tab. By writing a condition as a formula, you can filter the choices found in the column\u2019s dropdown lists. The attribute choice refers to the choices in the dropdown. Here, the formula is choice.Country == $Stadium_Country choice.Country looks at the value in the Country column of the Cities table. If it matches the value in the Stadium Country column of the Stadiums table, then that record will be included in the dropdown options. Now, instead of showing a list of all world cities, the dropdown list in the Stadium City column only lists choices that belong to the country entered in the Stadium Country column, making it much faster to select the appropriate choice. The choice attribute can also be used when setting dropdown filter conditions for choice and choice list columns. Note that because reference dropdown filtering conditions are written as formulas, these conditions can be very flexible and granular. Users experienced with access rules may notice similarities in how to think about writing these formulas.","title":"Filtering Reference choices in dropdown lists"},{"location":"conditional-formatting/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Conditional Formatting # Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style . Order of Rules # Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Conditional formatting"},{"location":"conditional-formatting/#conditional-formatting","text":"Cell style can change based on conditional rules. Conditional rules are written as formulas. Conditional formatting can apply to an entire row or cells in a column. To add conditional formatting to a particular column, select the column, go to the CELL STYLE section of the creator panel under the Column tab, and click on Add conditional style . In this example, we have a list of dog breeders who have raised champion thoroughbreds. Let\u2019s apply conditional formatting to the Breeder column based on the number of champion dogs. We would like to highlight in gold any breeders with more than 2 champions. Here the conditional formula is $Number_of_Champions > 2 . We would also like to highlight breeders with 1 or 2 champion dogs in blue, and 0 champion dogs in brown. Click Add another rule to add more conditional styles. To add conditional formatting to rows, go to the ROW STYLE section of the creator panel under the Table > Widget tab, and click on Add conditional style .","title":"Conditional Formatting"},{"location":"conditional-formatting/#order-of-rules","text":"Note that Grist applies the rules in order. Styles applied by later rules will override those applied by earlier rules. What would happen if we swapped the last two rules in the example above? Notice that Gen Hamamoto, who has 0 champion dogs, is not highlighted in brown. This is because after applying the second conditional style, $Number_of_Champions == 0 , Grist applied the third, $Number_of_Champions <= 2 , which applies to Gen Hamamoto as well and shades him blue.","title":"Order of Rules"},{"location":"timestamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Timestamp columns # Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily. A \u201cCreated At\u201d column # Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation. An \u201cUpdated At\u201d column # If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"Timestamp columns"},{"location":"timestamps/#timestamp-columns","text":"Sometimes it is useful to have columns that store when individual records were created and updated. This is useful later, for example to sort records by age or freshness. Grist lets you create such columns easily.","title":"Timestamp columns"},{"location":"timestamps/#a-created-at-column","text":"Suppose we want a column that stores when a record was created. As a first step, add a column called (for example) Created At and enter NOW() as its formula. Set the column type to DateTime (see Making a date/time column ) and choose how you\u2019d like the time and date to be shown. What we\u2019d like is for the Created At value to stay unchanged once set, and to be calculated only when a record is created. To make the values stay unchanged once set, we need to change column behavior from a Formula Column to a Data Column . Click on the Column behavior drop-down menu, and choose Convert column to data : Now, to calculate values when a record is created, select Apply to new records . And we\u2019re done! All new records will have Created At set with NOW() at the moment of their creation.","title":"A “Created At” column"},{"location":"timestamps/#an-updated-at-column","text":"If we want a column that stores when a record is updated (as opposed to created), the procedure is similar to that for a \u201cCreated At\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns to \u201ccount\u201d as updates and which to ignore. It is still possible for a user to manually edit cells in the Created At and Updated At columns. If you don\u2019t want that to be allowed, use access rules to forbid it. Here is an example of the new columns at work. One new record was added, for Unorthodox Delivery Methods , and a created and updated time were set for it automatically. Then the description of Cotton Candy vs Candy Floss was updated, and the updated time for that record was set automatically. It would also be straightforward to add Created By and Updated By columns. Making the Created At and Updated At columns as we did here filled them for all pre-existing rows with the current date and time. If you\u2019d rather these be left blank, simply do the Convert to data column step before setting a formula, and then set the formula in the Optional Formula area.","title":"An “Updated At” column"},{"location":"authorship/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Authorship columns # Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that. A \u201cCreated By\u201d column # Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it: An \u201cUpdated By\u201d column # If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"Authorship columns"},{"location":"authorship/#authorship-columns","text":"Sometimes it is useful to have columns that record who created individual records, and who last updated them. Grist lets you create such columns easily. It also automatically tracks document changes in the Activity tab of Document History, but nevertheless it is convenient to have that information in tabular form available to formulas and filters, and authorship columns let you do that.","title":"Authorship columns"},{"location":"authorship/#a-created-by-column","text":"Suppose we want to fill a column automatically with the name of the creator of each record as they are added. As a first step, add a column called (for example) Created By . In the column options in the side panel (see Columns for a refresher), click Set trigger formula action. Set user.Name as the column\u2019s formula. There are other possibilities, such as user.Email , a unique user.UserID , and so on. The user information available is the same as that in Access rule conditions . Time information is available as well (see Timestamp columns ). But let\u2019s stick with user.Name for now. Now, to set the column whenever a record is created, make sure that Apply to new records option is checked. And that\u2019s it! Now whenever a record is created, the Created At column will be set to the name of the user creating it:","title":"A “Created By” column"},{"location":"authorship/#an-updated-by-column","text":"If we want a column that stores who last edited a record (as opposed to its creator), the procedure is similar to that for a \u201cCreated By\u201d column , but instead of Apply to new records , select Apply on record changes . Then select Any field (assuming you want any change in a record to count as an update) and press OK . You can alternatively pick and choose which columns, when updated, will trigger the formula. Here is an example the the new column at work - when Cotton Candy v Candy Floss is updated,a user name appears for that record: It is still possible for a user to manually edit cells in the Created By and Updated By columns. If you don\u2019t want that to be allowed, use access rules to forbid it.","title":"An “Updated By” column"},{"location":"col-transform/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Column Transformations # Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation. Type conversions # When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d. Formula-based transforms # Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Transformations"},{"location":"col-transform/#column-transformations","text":"Grist offers two ways to transform all values in a column. One is to change the type of the column, and the other is to apply a formula-based transformation.","title":""},{"location":"col-transform/#type-conversions","text":"When converting between different column types, Grist has sensible default behavior, but makes that behavior easy to revise. For example, suppose you have a column of integers. To convert that column to text, open the column options as described in Specifying a type , and find the column type section. Change the column type to text in the dropdown. You\u2019ll notice that a \u201ccancel/revise/apply\u201d dialog opens beside the dropdown. To change how the conversion is done, click Revise . You\u2019ll see a formula box with the default conversion method, grist.Text.typeConvert($tally) . This means \u201cdo default conversion to text for the tally column\u201d. you can replace this with any formula you like. For example: Code for converting to unicode tally lines is left as an exercise to the reader. To preview the results of the conversion, click \u201cpreview\u201d. When you are satisfied with the conversion, click \u201capply\u201d. To abandon the conversion, click \u201ccancel\u201d.","title":"Type conversions"},{"location":"col-transform/#formula-based-transforms","text":"Spreadsheets are convenient tools for cleaning up data using formulas . For example, imagine you had zip codes that have lost leading zeros - you can easily reformat them with a quick formula: We could now freeze the results and delete the original data if we don\u2019t need it anymore. If you know you\u2019re going to throw away the original data like this, Grist offers column transformations as a faster way to systematically modify all cells of a column. Find the \u201cTransform\u201d section at the bottom of the column options side panel (see Columns for how to open this panel). When you click the orange \u201clightning\u201d button, Grist prompts you with a formula, return $zip in this case. You can edit this formula to make some change to the selected column. For example return $zip + 1 would add one to the zip code. You can preview the effect your formula would have, and when you are happy, hit \u201cApply\u201d. In our case, where we want to add leading zeros, we\u2019ll need to first change our column type to be Text (assuming it is currently Integer - if it Numeric convert to Integer first and then to Text to avoid decimal points). Once done, we can use our formula for adding leading zeros: When happy, press \u201cApply\u201d to replace the cell values with their new versions. Likewise, the response column could be transformed with the formula into true/false values with $response[0] == 'y' , and then set as a toggle column .","title":"Formula-based transforms"},{"location":"formulas/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formulas # Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options: Column behavior # When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state. Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details. Formulas that operate over many rows # If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel: Varying formula by row # Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price Code viewer # Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document. Special values available in formulas # For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel. Freeze a formula column # If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns. Lookups # Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for. Recursion # Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines. Trigger Formulas # Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Intro to formulas"},{"location":"formulas/#formulas","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. It even has an AI Formula Assistant to help write formulas. If you\u2019ve used spreadsheets before, or database expressions, you\u2019ll be on familiar territory - but there are some wrinkles you\u2019ll want to know about, so hang around. Let\u2019s start with a classic use of spreadsheets. Suppose you have a list of products you\u2019ve ordered, the quantity you ordered, and the unit price of each. You\u2019ve made a column to show the quantity times the unit price, but want the computer to do that part for you. Just select a cell in the column you want to fill, and hit = key to tell Grist you want to enter a formula, rather than a value. Did you notice, when you did that, the labels of the columns changed a little? \u201cProduct\u201d became \u201c$Product\u201d, and \u201cUnit Price\u201d became \u201c$Unit_Price\u201d. This is Grist telling you how to refer to those columns in your formula. Just type $Quantity * $Unit_Price . You\u2019ll find an auto-complete feature ready to help you. Or if you don\u2019t like typing, click on the Quantity column, type the multiplication symbol, and then click on the Unit Price column. Your formula should look like this: To control the column ID, like \u201c$Unit_Price\u201d, that\u2019s used in formulas, see Renaming columns . Press Enter , and your formula is applied to all cells in the column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . In Grist, a single formula applies to a whole column. You don\u2019t have to worry about filling it in for all rows, and can refer to values in the same row without fuss. You can format numeric columns to look better by setting column type to Numeric , and selecting suitable formatting options:","title":"Formulas"},{"location":"formulas/#column-behavior","text":"When we provide a formula for a column we tell Grist to update its value on every change in a document. We can no longer type a value into the cell, because its value is determined purely by the formula. A formula column is one of three possible column behaviors, which you can control using the COLUMN BEHAVIOR section in the creator panel: Data column maintains data, which you can manually update or clear, or optionally calculate using trigger formulas . Formula column always reflects the result of formula calculation, and is kept up-to-date by Grist. Empty column is a state for a new column. Typing any value into it will turn it into a Data Column, while typing in a formula will turn it into a Formula Column Using the COLUMN BEHAVIOR section, you can manually change the column behavior. The most common options are available as green action buttons at the bottom, and other options are available under the behavior menu. Depending on the current column behavior, those are: Set formula action converts an empty column to a formula column. Set trigger formula or Convert to trigger formula action sets a trigger on a column (more on triggers in the next Trigger formulas section ). Make into data column action converts an empty column to a regular data column. Convert column to data converts a formula column to regular data column (you can read more about this feature in the Freeze a formula column section ). Clear and make into formula clears all the data in a column and converts it to a formula column. (We say \u201cclear\u201d as a reminder that existing data in the column will be lost. They\u2019ll be replaced by the calculation results from the formula.) Clear and reset clears all data and completely resets the column to its initial Empty Column state.","title":"Column behavior"},{"location":"formulas/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Grist documents may use Python 2 or Python 3, see our Python guide for details.","title":"Python"},{"location":"formulas/#formulas-that-operate-over-many-rows","text":"If you are a spreadsheet user, you may find yourself wanting to have some special rows at the end of your table that have formulas different to the rest. In Grist, we\u2019d like you to consider adding a widget to your page instead. For common use cases, Summary tables may be exactly what you need. Or if you want to set things up yourself, you can add an extra table widget like this (see Page widgets for details): This is just another table, giving us a place to put formulas outside of the structure of the Materials table. For example, if we wanted to count how many products there are in that table, we could use this formula: len(Materials.all) Every table in your document is available by its name in formulas, as a UserTable . This formula uses the all method to access the rows of the table, but doesn\u2019t do anything with them but count them. Here\u2019s a formula to compute the average price, using the Excel-like function AVERAGE : AVERAGE(Materials.all.Price) The all method returns a RecordSet , which supports iterating over individual columns this way. Equivalently, we could use a Python list comprehension : AVERAGE(material.Price for material in Materials.all) If you are not familiar with Python, it is worth following a tutorial. There are thousands online, including this official one . Python will be useful to you for all sorts of data work, not just Grist. List comprehension is useful once we\u2019re doing anything nuanced. For example, here\u2019s a formula to list the names of products with a quantity greater than 80: [m.Product for m in Materials.all if m.Quantity > 80] This is a list comprehension, but now with a conditional. The result is a list, which is rendered as text in a cell. Python can help in other ways in your search for rows. For example, here\u2019s a formula to find the name of the product with the highest quantity: max(Materials.all, key=lambda m: m.Quantity).Product Formulas are case-sensitive, with Excel-like functions being all-caps ( MAX ), and regular Python generally all lowercase ( max ). For exact matches, there is a shortcut to avoid iteration called lookupRecords , or lookupOne for single matches. Just pass the the values of columns you require to be matched. For example, here is a formula to look up the product name of a material with a quantity of 52: Materials.lookupOne(Quantity=52).Product For very large tables, it is wise to use lookups as much as you can, rather than iterating through rows. Returning to our example document, you can now see how we calculated the Total Spent , Average Quantity , and Most Ordered Product columns: Column Formula Total Spent SUM(Materials.all.Price) Average Quantity AVERAGE(Materials.all.Quantity) Most Ordered Product max(Materials.all, key=lambda m: m.Quantity).Product Separating out calculations like this from the body of your data can take some getting used to, but working this way can help keep your document more organized. And it brings other advantages. For example we could switch the formatting of the summary widget via the side panel:","title":"Formulas that operate over many rows"},{"location":"formulas/#varying-formula-by-row","text":"Having a formula apply to all rows is convenient and reduces the changes of mistakes. If you need to have a column change its behavior on different rows, it is possible using a conditional in the formula. For example, here is a replacement for the Materials.Price formula that ignores the price and shows zero for products whose name ends in \u201c(Sample)\u201d: if $Product.endswith(\"(Sample)\"): return 0 else: return $Quantity * $Unit_Price","title":"Varying formula by row"},{"location":"formulas/#code-viewer","text":"Once you have a lot of formulas, or if you have been invited to a document and want to get an overview of its formulas, there is a code viewer available with a pure Python summary of the document.","title":"Code viewer"},{"location":"formulas/#special-values-available-in-formulas","text":"For those familiar with Python, here are the extra values available to you in Grist: rec is the current row. The $column syntax is shorthand for rec.column . The rec variable is of type Record . table is the current table, and is of type UserTable . Tables in your document are available by their name, and are also of type UserTable . Many extra spreadsheet functions are available, see the full function list . If your table or column has a space in its name, or other characters that are awkward in Python, those characters are replaced with an underscore. Auto-complete may help you if you\u2019re not sure. You can also control the \u201cids\u201d of columns and tables in the right side panel.","title":"Special values available in formulas"},{"location":"formulas/#freeze-a-formula-column","text":"If you\u2019d like to save the output of your formula as plain values, you can simply change column behavior from Formula Column to Data Column . First open the column options in the side panel: Now click on the Formula Column and select Convert column to data option. Notice that there is no = sign in the column cells any more, showing that it is no longer a formula. The cells will no longer change if other cells they used to depend on change. The original formula is saved but stays inactive. It may come useful again if you wish to convert the column back to a formula column, or use it as a Trigger Formula . The side panel has lots of other handy settings, such as cell formatting (number of digits after decimal point, color, etc). The options apply just as much to formula columns as to regular columns.","title":"Freeze a formula column"},{"location":"formulas/#lookups","text":"Grist functions lookupOne and lookupRecords are useful for enumerating subsets of your data. For example, suppose we added a Category column to our Materials table, and wished to list all products belonging to a specific category. We can do this using TABLE.lookupRecords , where TABLE is the table of interest, and supplying it with the column values to match. For example, Materials.lookupRecords(Category='Ship') , as here: If you are following on, see Adding a field for details of how to add a new field to a card. If you care about the order of results, lookupRecords takes an optional sort_by parameter. For example, we could use this formula to sort by the product name itself: Materials.lookupRecords(Category='Ship', sort_by='Product').Product If you want to sort by multiple columns, remember that you can create a hidden formula column that combines data in any way you like, and then sort by that. The order of records returned by lookupRecords may not match the order of rows you see in a table. To get that order, use sort_by='manualSort' . This is an internal column that is updated with the manually established sort order of rows. If you find yourself doing a lot of look-ups, please consider whether Summary tables and Summary formulas might be what you are looking for.","title":"Lookups"},{"location":"formulas/#recursion","text":"Lookups are handy for recursive formulas. Suppose we have a table counting how many events we have per day, and want to add a cumulative sum of those event counts. One way to do that is with a formula like this: yesterday = Events.lookupOne(date=$date - datetime.timedelta(days=1)) $events + (yesterday.cumulative or 0) For clarity, we\u2019ve split this formula into two lines. The first line makes a variable pointing to the row of the day before. The second line computes the value we want in the cell. Python note: the value of the last line is automatically returned (you could prefix it with return if you like). Notice the yesterday.cumulative or 0 . For the earliest row in the table, there will be no yesterday. In this case, lookupOne returns a special empty record, for which yesterday.cumulative will be None . If you\u2019d like to simplify this formula, or find yourself using the same lookup in multiple formulas, it would be worth making yesterday a reference column . Simply add a reference column, and give a formula for it that matches how we defined yesterday here. To actually enter this formula in a cell, you\u2019d use Shift + Enter to divide the lines.","title":"Recursion"},{"location":"formulas/#trigger-formulas","text":"Formula columns are great for calculated values \u2013 those determined by other data in the document. It may also be useful to store independent data in a column, but still use a formula to calculate it in some situations. This is exactly what Trigger Formulas offer. It is a very powerful feature that allows you to create a Timestamp or Authorship column, recalculate your data based on a set of conditions that you decide , clean data when a new value is entered, or provide sensible default value for a column. To create a Trigger Formula column, you first need to open the creator panel and click on the Set trigger formula action. If you want to convert an existing formula, use the Convert to trigger formula action available in the COLUMN BEHAVIOR section. To control when the formula is evaluated, use the two checkbox options below: Apply to new records triggers the formula only when a new record is created (a default cell value). Apply on record changes triggers the formula when a record is updated. Applying to new records is self-explanatory, the formula will be evaluated only once when you add a new record. It is a perfect solution to provide default values to the empty cells. Second option allows you to fine grain the conditions and specify which columns, when updated, will trigger the evaluation: You probably noticed the first option Current field . At first glance, you probably wonder: \u201cWhy would I want to trigger the column on its own change?\u201d. This option allows you to react to a value that is being entered into the column, just before it is saved! In the formula editor, you have access to two variables that are not available to regular formulas: value which is the value that a user wants to enter, user which represents a user object that is making the change (you will also see this in the Access rules section). This allows you to make your application even smarter, track when a record was updated , or see who made the last change to a row . Simple examples: Ensure that the value in a column is always written in capital letters: With the trigger formula of value.upper() , the value typed into this column will be converted to upper case automatically. Format a value that the user enters to sanitize the data before saving: With the formula like value if value.startswith(\"SK\") else \"SK\" + value , the value typed into this column will always be prefixed with \u201cSK\u201d. Overwrite a default value from a referenced table: You can use a formula like value or $Client.Phone , to provide a default value from a referenced table, but still allow the user to type a new one. In each of these examples, when the user tries to modify a cell, Grist (before updating the record) will evaluate the formula and store its result in the column instead of the value provided by the user. For a detailed, real-life example read our guide on how to create time and user stamps. For more information on formulas and trigger formulas, check out our webinar Trigger Formulas v. Formulas .","title":"Trigger Formulas"},{"location":"references-lookups/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Using References and Lookups in Formulas # Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor. Reference columns and dot notation # Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table. Chaining # If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name . lookupOne # Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table. lookupOne and dot notation # Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list. lookupOne and sort_by # When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care. Understanding record sets # Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas. Reference lists and dot notation # Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets . lookupRecords # You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table. Reverse lookups # LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas . Working with record sets # lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"References and lookups"},{"location":"references-lookups/#using-references-and-lookups-in-formulas","text":"Reference and Reference List columns in Grist allow one table to create an explicit reference to another. A common example of this is seen in the Class Enrollment template. On the Staff page, we have a list of staff members. On the classes page, we have a reference column labeled Instructor that references the records on our Staff page. Keep in mind, it\u2019s not just referencing the Full Name column but the entire record associated with the selected instructor.","title":"Using References and Lookups in Formulas"},{"location":"references-lookups/#reference-columns-and-dot-notation","text":"Using a Reference column within a formula can make it easy to get any data from the referenced record. To do this, we use dot notation. It uses the format $A.B where A is the name of the reference column and B is the name of the column in the referenced table that we want to pull data from. Let\u2019s see this in action on the Enrollment View page of the Class Enrollment template. Dot notation is used in the Class_Times column of the ENROLLMENTS table, found at the bottom right of the Enrollment View page. We can see that the Class_Times column is using a formula with dot notation. Using the format $A.B described above, we can figure out that Class is the name of the reference column and Times is the name of the column in the referenced table. Let\u2019s track this back to where it\u2019s pulling from - since the reference column is Class, we can look at that column\u2019s information to find out what table it is pulling from. The Class column references data from the Classes table. Therefore, the Class_Times column is pulling from the Times column of the Classes table.","title":"Reference columns and dot notation"},{"location":"references-lookups/#chaining","text":"If the reference lookup returns a reference, this can be chained. Perhaps we want to add the Instructor\u2019s phone number to the Enrollments table. We can use the Class reference column to pull the instructor\u2019s information from the Classes table. As you can see in the screenshot above, the instructor column is a reference column itself. If we follow the format from before, our dot notation would be $Class.Instructor but the Instructor column points to the entire record of the instructor so we need to tell it what information we want from this record, creating a chain. The instructor column references the Staff table so we navigate there to find out what column we need to pull information from in order to get the phone number. The column that contains the instructor\u2019s phone number is Phone. Putting this all together, our dot notation for the instructor\u2019s phone number would be $Class.Instructor.Phone What happens if we leave our formulas as $Class.Instructor ? You will see a numeric record ID of the record in the Staff table that the Instructor column points to. That\u2019s what a reference column really stores. If you change the type of this formula column to Reference, you will be able to select a column to show, such as the Full Name. Another way to see the name is to chain the dot-notation, as we did for phone: $Class.Instructor.Full_Name .","title":"Chaining"},{"location":"references-lookups/#lookupone","text":"Another way to point to a record is using Table.lookupOne(...) function. lookupOne allows you to look up a record by some fields, similar to Excel\u2019s VLOOKUP. In fact, Grist\u2019s version of VLOOKUP is merely an alias for lookupOne. lookupOne is rarely useful in Grist, because using a Reference type column is usually the preferred solution to connect records. However, on some occasions, lookupOne can be useful. One situation is when you have two sets of data which overlap even though they represent something different and perhaps come from different sources. An example of this can be found in our Event Sponsors + Attendees (References and Lookups) document which is a modified version of the Event Sponsors + Attendees template, available in our template gallery . Let\u2019s say that you run an event and have a list of registered attendees, as well as Sponsors. Registered attendees are stored in the All Registrations table, perhaps populated via a form integration. Sponsors are listed in a separate table, with fields related to their sponsorship, and perhaps maintained by another team. Both tables contain email addresses which identify attendees and sponsors. Sometimes a sponsor may register to attend the event. In that case, you\u2019ll have an Attendee record with an email address that also appears in the Sponsors table. That\u2019s useful to know for someone looking at the attendee list. You can lookup a record in the sponsors table by email address by using a lookupOne formula. The Sponsor column in the All Registrations table does just that using this formula: Sponsors.lookupOne(Contact_Email=$Registration_Email) This formula is looking to see if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. The general format for a lookupOne formula is: [Table_Name].lookupOne([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Such a formula returns a reference. In the screenshot above, you can see the lookup result returns Sponsors[#] . The number it returns between square brackets is the record ID of the lookup result. Where it returns Sponsors[0] , no match was found. It\u2019s often a good idea to create a column for the lookup result and change its type to Reference, as you see in the screenshot below. Then, if there is a match, the reference column will point to the entire matched record. Like any reference column, you can select which field from that record to show. In this example, it shows the Company field of the matched record in the Sponsors table.","title":"lookupOne"},{"location":"references-lookups/#lookupone-and-dot-notation","text":"Because lookupOne is creating a reference to a record, we can use dot notation to look up additional fields in that record. In the example above, Sponsors.lookupOne(Contact_Email=$Registration_Email) is checking if a Contact Email from the Sponsors table matches a Registration Email from the All Registrations table. What if we also wanted to look up the sponsor level? We can add .Sponsor_Level to the lookupOne formula, and, if a match is found, look up the value in the sponsor level column for the matched record. The entire formula would be Sponsors.lookupOne(Contact_Email=$Registration_Email).Sponsor_Level . Now, we have the Sponsor Level listed in the All Registrations table for those attendees whose emails also appear on the sponsor list.","title":"lookupOne and dot notation"},{"location":"references-lookups/#lookupone-and-sort_by","text":"When the lookupOne function encounters multiple matching results, it returns the first one by row ID. The optional sort_by parameter can be used to sort these results by another field, to determine which one would be returned as the first match. You can also prefix the column ID with \u201c-\u201d to reverse the order. For instance, consider this example from the Class Enrollment template. This template tracks enrollment for extracurricular and other classes - logging information for students, families, and staff. On this page, we have a list of students and their respective information. Additionally, we have a Families page that outlines the parent of each student and we\u2019d like to find which student in each family is the oldest. So, we would create an oldest student column. Then, the following formula would look at the Students table, find the specific students associated with each family, sort them by their birthday, and return the one student with the earliest birthday: Students.lookupOne(Family=$id, sort_by=\"Birthday\") In this case, this would return: Raddon, Brockie. Alternatively, if we want to find the youngest student, the formula would include \u201c-\u201c: Students.lookupOne(Family=$id, sort_by=\"-Birthday\u201d) In this case, this would return: Raddon, Care.","title":"lookupOne and sort_by"},{"location":"references-lookups/#understanding-record-sets","text":"Sometimes a record may reference multiple records in another table. Multiple references can be made with a Reference List Column. A great example of this is seen on the Habit Tracker template. On the Habits + Goals page, we have a list of habits and a goal for how often we wish to complete that habit. On the Habit Tracker page, we have a Reference List column labeled Habits Completed that references the records on our Habits + Goals page. The only difference between a Reference column and a Reference List column is the ability to select multiple references. This creates a set of records which can be used in formulas.","title":"Understanding record sets"},{"location":"references-lookups/#reference-lists-and-dot-notation","text":"Similar to references, you can use Dot Notation with reference lists. Building on our prior example of attendees at a conference, suppose we have a list of registrants for an event and want to find the balance for each registrant. To do this, we can use dot notation. Here, $Registrants is a reference list. Our Great Outdoors Expo has 4 registrants. We can see the list of registrants in the Registrants column. This list is a reference to the Name column of the All Registrants table. With a reference list, dot-notation returns a list of all the selected field; $Registrants.Balance is a list of the Balances for each attendee in the list of $Registrants . This follows the format $[A].[B] where [A] is the name of the Reference List column and [B] is the name of the column in the referenced table you wish to pull data from. We\u2019ll learn how to find the sum of these balances in Working with Record Sets .","title":"Reference lists and dot notation"},{"location":"references-lookups/#lookuprecords","text":"You can also get a list of references using lookupRecords . The formula for lookupRecords follows this format: [Table_Name].lookupRecords([A]=$[B]) [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Suppose we want a list of the events attended by each person in our Email List table. We can use lookupRecords to do this. First, we need to lookup records where the email listed in the All Registrations table matches an email in this list. Then, find the event associated with each of those records. Following the format above, our initial formula is: All_Registrations.lookupRecords(Registration_Email=$Email) All_Registrations.lookupRecords(Registration_Email=$Email) returns a list of record IDs for each record in the All Registrations table where the Registration Email matches the Email in this row of the Email List table. Next, we need to find the Event associated with each of these records. To do this, we can use dot notation. All_Registrations.lookupRecords(Registration_Email=$Email).Event will return the value from the Event column for each record found. We saw similar results using the lookupOne function. It\u2019s helpful to change the column type to Reference List, as you see in the screenshot below. Then, if there is a match, the reference list column will point to the entire record for each match. Like any reference list column, you can select which field you want to show for the matched records. In this example, it shows the Event field of the Events table for each matched record in the Attendees table.","title":"lookupRecords"},{"location":"references-lookups/#reverse-lookups","text":"LookupRecords works a bit differently if a reference exists between two tables. With a reverse lookup, we can use the record ID to find a record. Every row has a numeric id (available as $id in formulas) that is unique within that table. You can reveal the ID by adding a formula column where formula is $id Let\u2019s take a look at the Registrants column of the Events table. The formula used here is All_Registrations.lookupRecords(Event=$id) . We use the id to find a match because in the All Registrations table, the Event column is a reference column which means its value is a record\u2019s id. Because All_Registrations.Event is a reference column pointing to an Event record in the Events table, we can match the id stored in the reference column to the ids of records in the Events table. That\u2019s why the argument in the formula is Event=$id . We use the existing reference, just in reverse - hence the name, Reverse Lookup. If you\u2019d like a video walkthrough of a reverse lookup, we have an example in our Build with Grist Webinar - Trigger Formulas v. Formulas .","title":"Reverse lookups"},{"location":"references-lookups/#working-with-record-sets","text":"lookupRecords can also be used within other formulas. SUM() can be useful to find a sum of all numbers in a list of records. Once you find your list of records using the lookupRecords function and dot notation, you can use SUM() to sum all values returned, like you see in this formula: SUM(Table.lookupRecords(Column_A=$Column_B).Column_C) You can also do this on a reference list because a reference list is the same thing, a list of records. SUM($RefList.Column) In the Reference lists and dot notation section, we used the Registrants column and dot notation to find the balance for each person in our list of Registrants. We can use SUM() with our prior formula to find the total balance. SUM($Registrants.Balance) We can also use lookupRecords to get the list of references, rather than using a reference list column, then find the sum of the balance for all registrants. This method is used in the Ticket Revenue column of the Events table using the following formula: SUM(All_Registrations.lookupRecords(Event=$id).Balance) All_Registrations.lookupRecords(Event=$id).Balance finds all records in the All Registrations table where the Event column matches the ID of the row in this table, Events. Using dot notation, we find the Balance for each of the records found. Then SUM() sums the balances of all records found. You can also iterate through a Reference List using a Python for loop. An example of this can be seen in the Balance (\u2018for\u2019 loop) column in the Events table. When iterating, each element is a Reference so dot-notation can be used here as well. To find the sum of the balance for all registrants, we use the following formula: SUM(person.Balance for person in $Registrants) This does the same thing as our lookupRecords formula we saw above. $Registrants is our reference list. For each record ( person ) in our list of Registrants, we find the Balance. Then, sum all balances together. In this formula, person is a variable that represents each element in our list and could be replaced with any other variable. If you\u2019d like to learn more about Data Structures and List Comprehension in Python 3, Python.org is a great resource. len() can be useful to get the number of items within a list. Once you find your list of records using the lookupRecords function, you can use len() to count the number of records returned, like you see in this formula: len(Table.lookupRecords(Column_A=$Column_B)) You can also do this on a reference list. len($RefList) We want to see how many events our Sponsors have attended. We can use lookupRecords to do this. The following formula is used in the Events Attended column of the Sponsors table. len(All_Registrations.lookupRecords(Sponsor=$id)) Let\u2019s break down the two parts of this formula, working from the inside out. All_Registrations.lookupRecords(Sponsor=$id) is looking for matches where the record in the Sponsor column of the All Registrations table has the same ID as the record in this row of the Sponsors table. All records in the All Registrations table that match are added to a list of records. Try writing the formula without len() to see what Grist returns. It should look something like this. That\u2019s a list of records. len() counts how many records are in that list. We can also include multiple arguments in a lookupRecords formula. An example of this can be found in the Count column of the Classes table of the Class Enrollment template. This column shows us how many students are enrolled in each class. The formula used here is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This lookup uses two fields. It will look for records in the Enrollment table where Status is \u201cConfirmed\u201d and the Class column matches the ID of the row in this table. Because the Class column is referencing the Classes table, we use the record ID $id in the lookup. Finally, len() counts the items in the list returned by Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\") . Average, min and max are a few of the other functions that can be used with lookupRecords formulas. See all available functions on our Function reference page.","title":"Working with record sets"},{"location":"dates/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview # Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them. Making a date/time column # For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times. Inserting the current date # You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date. Parsing dates from strings # The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True) Date arithmetic # Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more . Getting a part of the date # You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ). Time zones # Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone. Additional resources # Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Working with dates"},{"location":"dates/#overview","text":"Grist expresses dates and times in two ways. The first is the Date column type, which represents a calendar date, with no time of day, and not associated with any particular timezone. The second is the DateTime column type, which represents a calendar date with a time of day which can be linked with a timezone. The Date and DateTime column types support different formatting options. When a column is set to be a Date or a DateTime , a date-picker widget will let you select the date on a calendar when editing a cell. When working with dates in formulas, the dates are Python datetime objects . That allows you to do some powerful things, but can be unexpected if you\u2019re not familiar with them.","title":"Overview"},{"location":"dates/#making-a-datetime-column","text":"For a general introduction to setting the type of columns, see Columns and data types . To tell Grist that you intend to enter only date/times in a column, over on the header for the column, find the drop-down, and select \u201cColumn Options\u201d. Then in the side panel that opens on the right, pick \u201cDate\u201d from the \u201cColumn Type\u201d drop-down. Or, if you want dates with times, pick \u201cDateTime\u201d. Then you can choose your preferred date/time format. For the \u201cDateTime\u201d type, you can also choose the timezone. When you convert a column from another type, such as \u201cText\u201d, you\u2019ll see a preview of the conversion results, and will need to click \u201cApply\u201d to complete conversion. You can come back and change settings at any time. Now when you edit a cell in this column, you will have help for selecting dates and times.","title":"Making a date/time column"},{"location":"dates/#inserting-the-current-date","text":"You can insert the current date in a cell using \u2318 + ; (semicolon) (Mac) or Ctrl + ; (Windows). You can insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows). When editing a date cell, the date entry widget has a \u201ctoday\u201d button for today\u2019s date.","title":"Inserting the current date"},{"location":"dates/#parsing-dates-from-strings","text":"The DATEVALUE function converts a string that represents a date into a datetime object. It\u2019s simple to use and it will auto-detect different date formats: You can also use Python\u2019s datetime library, which provides two helpful functions: strptime() and strftime() . For example, let\u2019s say you have a table of movie sequels and their release dates (as strings). You\u2019d like to parse out the actual date to be able to sort the table properly. Here\u2019s how you would do that: First line imports the datetime library The second line splits the string into two parts and returns the second part (Python arrays are zero-based). The third line uses Python\u2019s strptime function to parse the date (e.g. \u201cMay 19, 1999\u201d) into a datetime object. The first parameter to the function is the string to parse, the second parameter is the date format that the string is in. Take a look at the format options to see if the example format string %B %d, %Y makes sense. (Note: You could\u2019ve also used DATEVALUE(d) to achieve the same result.) The result has a true date column and can now be properly sorted chronologically, with \u201cA New Hope\u201d at the top. For historical reasons, the first Star Wars movie is considered to be Episode 4. And, because the column type is selected as a date, you can use the \u201cDate Format\u201d in \u201cColumn Options\u201d to select the format in which to display the date. For some situations, you may wish to use the dateutil python library. For example, if you live in an area where dates typically start with the day and then the month, you could use this formula: import dateutil dateutil.parser.parse($date_text, dayfirst=True)","title":"Parsing dates from strings"},{"location":"dates/#date-arithmetic","text":"Once you have a proper date column, often you\u2019ll want to do date arithmetic such as calculating the difference between two dates. The simplest way to do this is to use the DATEDIF function which takes two dates and the unit of information to return (Days, Months, or Years). You could also use the minus sign to subtract two dates, but you might be surprised at the result: This happens because subtracting two datetime objects as we did in the example above, results in a datetime.timedelta object which represents, \u201cA duration expressing the difference between two date, time, or datetime instances to microsecond resolution.\u201d In Grist (and Python) you have to be more specific above how you want to display the date difference. For example, to get the number of days from the returned timedelta object, use its .days property: If you want weeks or years, just divide by 7 or by 365. (Divide by 7.0 or 365.0 to include a fractional part in the result.) If you want hours, multiply by 24. You can also use specific functions to get what you want. For example, DAYS is a common function in spreadsheet apps that returns the difference between two dates: DAYS($Last_day, $First_day) Excel/Sheets formulas Grist supports many other common functions from other spreadsheet apps, including DATEADD , DATEDIF , DATEVALUE , MONTH , HOUR , and many more .","title":"Date arithmetic"},{"location":"dates/#getting-a-part-of-the-date","text":"You\u2019ve seen how to parse the date, display it in different formats, and do date arithmetic. But what if you want to get more information about a specific date, such as getting its day of the week? One option is to use the WEEKDAY function, which behaves as it does in Excel, returning 1-7 for Sunday-Saturday. Alternatively, we can use the strftime function: Yet another option would be to reformat the date using Date Format in Column Options (see the date formatting reference ).","title":"Getting a part of the date"},{"location":"dates/#time-zones","text":"Values in DateTime columns represent moments in time. The same moment will look different in different timezones. In Grist, the timezone is set on each DateTime column. For instance, if the timezone is set to \u201cAmerica/New_York\u201d, it will show the values in New York timezone to collaborators anywhere in the world. A Grist document has a global timezone setting, which serves as the default timezone for when you create a new column of type DateTime . This global timezone is set to your local timezone when you first create a document. You can see or change it by clicking on your profile picture or icon, and selecting \u201cDocument Settings\u201d. If you insert the current date and time using \u2318 + Shift + ; (Mac) or Ctrl + Shift + ; (Windows) into a DateTime column, it will be inserted as a true timezone-aware timestamp, and shown with the timezone set for that column. If you do the same in a Text column, the date/time will be inserted as the text appropriate for the document\u2019s global timezone setting. Similarly, inserting the current date into a Date column will produce the current date according to the document\u2019s timezone.","title":"Time zones"},{"location":"dates/#additional-resources","text":"Python cheatsheet for strftime , for using with strftime() and strptime() in formulas. Date formatting cheatsheet , for specifying the date/time format in column settings. dateutil library , extensions to the Python standard datetime module.","title":"Additional resources"},{"location":"formula-timer/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula timer # Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document. Results # Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Formula timer"},{"location":"formula-timer/#formula-timer","text":"Grist has a built-in formula timer that will measure the time it takes to evaluate each formula in a document. This helps diagnose which formulas are responsible for slow performance when a document is first opened, or when a document responds to changes. Grist\u2019s formula timer can be found on the \u2018Document Settings\u2019 page, under \u2018Data Engine\u2019. Select \u2018Start timing\u2019 to begin. On the next screen, you will have two choices, \u2018Start timing\u2019 and \u2018Time reload\u2019. Start timing \u2018Start timing\u2019 allows you to make changes to the document then stop timing to see the results. This is useful if you want to test specific formulas. You can make a change that affects that formula then come back and click \u2018Stop timing\u2019 to see the result. Time reload \u2018Time reload\u2019 forces a reload of the document while timing formulas and shows the result. This will show timing results for all formulas across the entire document.","title":"Formula timer"},{"location":"formula-timer/#results","text":"Results are displayed in a table format. \u26a0\ufe0f Results Table The Formula Timer results table is not saved anywhere in the document. If you click away from this page, you will need to run the formula timer again to retrieve the table. Sort the Total Time column from Z > A so the formulas that take the longest time to run are listed first. The table specifies the Table ID and Column ID containing each formula. Review the formulas with the highest total time to see how they can be improved. If your document is experiencing slowness due to formula calculations, you\u2019ll see Total Times greater than 1 second. Need guidance on how to improve a formula? Post to our Community Forum !","title":"Results"},{"location":"python/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Python # Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem. Supported Python versions # We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions. Testing the effect of changing Python versions # Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all. Differences between Python versions # There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas. Division of whole numbers # In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional! Some imports are reorganized # Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus Subtle change in rounding # Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2) Unicode text handling # Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Python versions"},{"location":"python/#python","text":"Grist formulas are written in Python, the most popular language for data science. The entirety of Python\u2019s standard library is available to you. For those with a spreadsheet background, we\u2019ve also added a suite of Excel-like functions, with all-uppercase names. Here\u2019s the full list of functions . Python formulas are evaluated in a sandbox, without internet access, and without a persistent filesystem.","title":"Python"},{"location":"python/#supported-python-versions","text":"We currently support two versions of Python: Python 3 (specifically 3.11 at the time of writing) Python 2 (specifically 2.7) Newly created documents on our hosted service use Python 3 by default, while older documents (created before November 2021 approximately) use Python 2 by default. To tell which version of Python a specific document uses, look at its Document Settings . The Engine setting may be python2 , python3 , or blank. A blank setting implies python2 . If you have editing rights on a document, you can change the Engine setting, and the document will then reload with all formulas now interpreted using the version of Python you have specified. We recommend caution in doing so. A formula that works as intended in one version of Python may give errors in another, or (worse) give the wrong results. Warning Some formulas may fail or give wrong results if used with a version of Python that is different from the one for which they were written. Python 2 reached its end of life in January 2020, so if you look online for python help, the answers you find are more and more likely to be for Python 3. If you have a document that uses Python 2, and you\u2019d like to switch it to use Python 3, we recommend reading Testing the effect of changing Python versions and Differences between Python versions . Be sure to check all tables and columns, and both regular formalas and trigger formulas. We\u2019d be interested to hear your experience, and to help with any problems, on the community forum . Self-hosted Grist may use any version of Python you configure it with, but bear in mind we actively test only the supported versions.","title":"Supported Python versions"},{"location":"python/#testing-the-effect-of-changing-python-versions","text":"Grist has some features that can help you evaluate the consequences of changing the Python version a document uses. The Work on a Copy feature is useful to experiment with changing the Python version without affecting your document until you are ready. There is a \u201cCompare with original\u201d option that will let you visualize which cells changed, if any. Be sure to look at all tables and columns. The Activity tab of Document History (with \u201cAll Tables\u201d selected) lets you review in more detail what has changed. Be careful to test any trigger formulas you may have, since the Python code in them won\u2019t be exercised until you specifically trigger these formulas. You can use the code viewer to quickly remind yourself of all formulas in a document, so you can systematically check them all.","title":"Testing the effect of changing Python versions"},{"location":"python/#differences-between-python-versions","text":"There are important differences between Python 2 and 3. Formulas may need to be changed in order to give the same results when switching between Python versions. There are many online resources such as this compatibility cheatsheet which can help figure out what the issue is when you hit a difference, and get ideas on how to resolve it. Here, we list common cases we\u2019ve seen in Grist formulas.","title":"Differences between Python versions"},{"location":"python/#division-of-whole-numbers","text":"In Python 2, dividing whole numbers gives a whole number, so 9 / 2 is 4 . In Python 3, it is 4.5 . For a spreadsheet, this is a much more sensible answer, but if you rely on the Python 2 behavior, we suggest you switch to the // operator which is consistent between versions ( 9 // 2 is 4 for both). For example the General Ledger template had a Python 2 formula for computing the quarter from a date (so a Date of 2021-08-15 gave a Quarter of 2021 Q3 ) as follows: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) / 3) when switching to Python 3, this needed correcting to: \"%s Q%s\" % ($Date.year, CEILING($Date.month, 3) // 3) Otherwise Quarters became fractional!","title":"Division of whole numbers"},{"location":"python/#some-imports-are-reorganized","text":"Python has a useful standard library, but some parts of it were moved around between Python 2 and 3. For example, several of our templates have formulas to construct URLs, to open custom searches for example, or to open a pre-populated email with calculated To , CC , and Subject values. Python has handy helpers for constructing URLs, but they moved around a bit between Python versions. Our Lightweight CRM example had a Python 2 formula like this to kick off a search for emails in Gmail: from urllib import quote_plus \"Gmail search https://mail.google.com/mail/u/0/#search/%s\" % quote_plus($Email) In Python 3, the import line needed changing to: from urllib.parse import quote_plus","title":"Some imports are reorganized"},{"location":"python/#subtle-change-in-rounding","text":"Python 3 switches the built-in function round() from rounding the way many people learned in school (where when rounding .5 , you always round up) to what is called \u201cbanker\u2019s rounding\u201d (where you round from .5 to the nearest even number). This is generally accepted as an improvement, mitigating a bias to larger numbers that can become significant at scale. But it could be a surprise to see numbers change like this in an established document. If you really need Python 2\u2019s rounding, replace any calls to Python\u2019s round function with the Excel-compatible ROUND function. For example: round($val, 2) would be replaced with: ROUND($val, 2)","title":"Subtle change in rounding"},{"location":"python/#unicode-text-handling","text":"Python 2 does not shine at handling international text and emojis. We have mitigated many problems by setting the default encoding to utf8 for all documents. Nevertheless, when switching from Python 2 to Python 3, you may see type changes or errors. Consider this Python 2 formula to generate a one-way hash of an email address: import hashlib hashlib.sha256($Email).hexdigest() In Python 3 this fails with TypeError: Unicode-objects must be encoded before hashing , which can be resolved by replacing $Email with $Email.encode() : import hashlib hashlib.sha256($Email.encode()).hexdigest()","title":"Unicode text handling"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"formula-cheat-sheet/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Formula Cheat Sheet # Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out! Math Functions # Simple Math (add, subtract, multiply divide) # Uses + , - , / and * operators to complete calculations. Example of Simple Math # Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly. Troubleshooting Errors # #TypeError : Confirm all columns used in the formula are of Numeric type. max and min # Allows you to find the max or min values in a list. Examples using MAX() and MIN() # MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format . Sum # Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables . Example of SUM() # Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables Comparing for equality: == and != # When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True . Examples using == # Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned. Examples using != # Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False . Comparing Values: < , > , <= , >= # Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss . Examples comparing values # Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false. Converting from String to Float # String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number. Example converting a string to a float # Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float. Troubleshooting # if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved. Rounding Numbers # Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47 Example of rounding numbers # Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 . Formatting numbers with leading zeros # Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 . Formatting numbers with leading zeros # Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified. Troubleshooting Errors # #TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() . Working with Strings # Combining Text From Multiple Columns # Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in. Examples using Method 1 # Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU. Examples using Method 2 # Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line. Splitting Strings of Text # Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] . Example of Splitting Strings of Text # Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split . Direct Link to Gmail History for a Contact # If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact Troubleshooting # Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink. Joining a List of Strings # When you want to join a list of strings, you can use Python\u2019s join() method . Example of Joining a List # Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space. Finding Duplicates # You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates Example of Finding Duplicates # Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged. Using a Record\u2019s Unique Identifier in Formulas # When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id . Examples Using Row ID in Formulas # You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record. Removing Duplicates From a List # You can remove duplicates from a list with help from Python\u2019s set() method. Example of Removing Duplicates from a List # Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) ) Setting Default Values for New Records # You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget Working with dates and times # Automatic Date, Time and Author Stamps # You can automatically add the date or time a record was created or updated as well as who made the change. Examples of Automatic Date, Time and Author Stamps # Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account. Troubleshooting Errors # If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem. Filtering Data within a Specified Amount of Time # Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter. Example Filtering Data that \u2018Falls in 1 Month Range` # Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values. Troubleshooting Errors # #TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Formula cheat sheet"},{"location":"formula-cheat-sheet/#formula-cheat-sheet","text":"Grist has a powerful data engine to calculate the cells of your tables using formulas. Grist formulas are written in Python , the most popular language for data science. We also have a suite of Excel-like functions , with all-uppercase names. Here are some helpful notes: Formulas apply to the entire column Fields are included in formulas as $ColumnID . Python is case-sensitive, including for Grist table and column names. If your column ID is title , the formula will use $title , where both are lowercase. You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. If you don\u2019t see what you\u2019re looking for, post in the Community Forum and we\u2019ll be able to help you out!","title":"Formula Cheat Sheet"},{"location":"formula-cheat-sheet/#math-functions","text":"","title":"Math Functions"},{"location":"formula-cheat-sheet/#simple-math-add-subtract-multiply-divide","text":"Uses + , - , / and * operators to complete calculations.","title":"Simple Math (add, subtract, multiply divide)"},{"location":"formula-cheat-sheet/#example-of-simple-math","text":"Chestwood Art Studio ships art across the country and has the option of monthly payments over the course of 12 months. We have the subtotal, the tax (based on the state it is shipping to) and Amount Due Monthly. This formula column uses addition, multiplication and division. The formula used here is: ($Subtotal + ($Subtotal*$Tax)) / 12 We add the subtotal to the calculated tax then divide this by 12 months to get our Amount Due Monthly.","title":"Example of Simple Math"},{"location":"formula-cheat-sheet/#troubleshooting-errors","text":"#TypeError : Confirm all columns used in the formula are of Numeric type.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#max-and-min","text":"Allows you to find the max or min values in a list.","title":"max and min"},{"location":"formula-cheat-sheet/#examples-using-max-and-min","text":"MAX() and MIN() when capitalized are spreadsheet functions which require a specific syntax. Spreadsheet formula syntax is summarized in our functions reference . max() and min() in lowercase are Python functions. Max : Classes table of the Class Enrollment template. The formula used in the \u2018Spots Left\u2019 column of the Classes table is: max($Max_Students - $Count, 0) or \"Full\" This formula shows the number of spots remaining in a class, or the text \u2018Full\u2019 when the class is full or oversubscribed. We build a list between the parenthesis consisting of two items: $Max_Students - $Count and 0 . The formula returns whichever is greater. When $Count is less than $Max_Students , the difference $Max_Students - $Count is positive and represents the spots left in the class. When $Count exceeds $Max_Students , then the class is full or oversubscribed, and $Max_Students - $Count is negative. The maximum of a negative number and 0 will be 0, so max($Max_Students - $Count, 0) is 0. This represents a full class. The addition of or \"Full\" is applied when the value is falsy, which means that a 0 is replaced with the text \"Full\" . Min : Contacts table of the Lightweight CRM template. The formula used in the \u2018Due\u2019 column of the Contacts table is: items = Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") return min(items.Date) if items else None Let\u2019s break this down. Interactions.lookupRecords(Contact=$id, Type=\"To-Do\") finds all records in the Interactions table where the Contacts match and the Type is To-Do. This returns a list of records that we assign to the variable items . Next, we use dot notation to find all Dates assigned to the records in our items list. These dates are evaluated to find the minimum date. This is the value that is returned. So, we see the date of the task that is due the soonest. If there are no items in the list, nothing is returned and the field is left blank. In the MAX() example, the list has two items: $Max_Students - $Count and 0 , and the formula returns whichever is greater. In the min() example, the variable items is pulling a list of records based on the lookupRecords arguments, listing the dates, and returning the smallest date. Note that this is a Python function. If we had written the formula as MIN(), a spreadsheet function, the formula would not work because the spreadsheet formula requires a very specific format .","title":"Examples using MAX() and MIN()"},{"location":"formula-cheat-sheet/#sum","text":"Use the SUM() function when you want to sum a list of values available within a cell. If you want to sum values in a column, use Summary Tables .","title":"Sum"},{"location":"formula-cheat-sheet/#example-of-sum","text":"Custom Product Builder template The formula used in the Total Cost column of the Select or Add New Products table is: SUM($Requirements.Cost) The Requirements column is a hidden column in this table. It is a reference list column that pulls data from the Build Requirements table. Our formula uses the Requirements column to access the Build Requirements table then pulls the cost for each record in the table. We use SUM() to sum the costs from each record. Inventory Manager template The formula used in the Received column of the All Products table is: SUM(Incoming_Order_Line_Items.lookupRecords(SKU=$id).Received_Qty) We use the lookupRecords function to find all records in the Incoming Order Line Items table where the SKU matches the SKU in this row then pull the value in the Received Qty column for each of those records. We use SUM() to find the sum of those values. The Qty on Order and Sold columns of the All Products table are also great examples of the SUM() function. Check out another example in our Community Forum: Creating a Sum of Net and Gross profit from multiple tables","title":"Example of SUM()"},{"location":"formula-cheat-sheet/#comparing-for-equality-and","text":"When comparing for equality in Python, we use == for \u2018equals\u2019 and != for \u2018does not equal\u2019. If $A is 2 and $B is 3, the formula $A == $B would return False , while the formula $A != $B would be True .","title":"Comparing for equality: == and !="},{"location":"formula-cheat-sheet/#examples-using","text":"Inventory Manager template The formula used in the Received Qty column of the Incoming Order Line Items table is: if $Order.Status =='Received': return $Qty else: return None The Order column of the Incoming Order Line Items Table is a reference column that points to the Order Number column of the Incoming Orders table. $Order.Status uses dot notation to pull the value from the Status column of the Incoming Orders table. If the value in this column is equal to Received , the value from the Qty column will be returned. If the value is not equal to Received , nothing is returned. The formula used in the Date Received column of the Create New Order table is: if $Status == \"Received\": return NOW() This is a trigger formula that is triggered when a change is made to the Status column. If the value in the Status column is equal to Received , the current date is returned. If the values are not equal, nothing is returned.","title":"Examples using =="},{"location":"formula-cheat-sheet/#examples-using_1","text":"Project Management template The formula used in the Missed Deadline column of the Missed Deadline table is: TODAY()> $Due_Date and $Status != \"Completed\" If the current date is greater than the date given in the Due Date column and the value in the Status column is not equal to Completed , the formula is True . If either of these statements is false, the formula is False .","title":"Examples using !="},{"location":"formula-cheat-sheet/#comparing-values","text":"Allows you to compare numerical values. If Sales is equal to 1200 and Running_Cost is equal to 1650 , \"Gains\" if $Sales > $Running_Cost else \"Loss\" would return Loss .","title":"Comparing Values: < , > , <= , >="},{"location":"formula-cheat-sheet/#examples-comparing-values","text":"Inventory Manager template The formula used in the Stock Alert column of the All Products table is: if $In_Stock + $QTY_on_Order > 5: return \"In Stock\" if $In_Stock + $QTY_on_Order > 0: return \"Low Stock\" else: return \"OUT OF STOCK\" Here, we have two different if-return statements; if x is true, return some_value . Once a statement is true and a value is returned, the formula stops. If both are false, OUT OF STOCK is returned. First, if the value in the In Stock column plus the value in the Qty On Order column are greater than 5, return \u201cIn Stock\u201d. Next, if the value in the In Stock column plus the value in the Qty On Order column are greater than 0, return \u201cLow Stock\u201d. It\u2019s implied that the value is less than or equal to 5 because the first statement would have to be false for this to be evaluated. Last, if all statements are false, return \u201cOUT OF STOCK\u201d. Internal Links Tracker for SEO template The formula used in the Orphaned? column of the Orphaned Pages table is: len(Links.lookupRecords(To=$id))<1 We use the lookupRecords function to find all records in the Links table where the link in the To column matches the link listed in the Slug column of this row. We use len() to count the number of records found. If it\u2019s less than 1, the formula is evaluated to be true and the checkbox will be checked. If it\u2019s equal to or greater than 1, the formula is evaluated to be false.","title":"Examples comparing values"},{"location":"formula-cheat-sheet/#converting-from-string-to-float","text":"String : A sequence of characters or snippets of text. In code, strings are quoted e.g. 'Hello' or \"-12\" (those are three characters in quotes, as opposed to a negative number). See Python str() Function for converting a specified value to a string. Float : Real numbers that can store decimal values. Also called floating point number. See Python float() Function for converting a specified value into a floating point number. Integer : A whole number, without decimals. See Python int() Function for converting a specified value to an integer number.","title":"Converting from String to Float"},{"location":"formula-cheat-sheet/#example-converting-a-string-to-a-float","text":"Artwork Orders The formula used in the Sale Price column is: if $Appraisal_Value.endswith(\"k\"): return float($Appraisal_Value.rstrip(\"k\")) * 1000 return float($Appraisal_Value) In this example, the Appraisal Value column is a text column that contains alpha-numeric characters. In order to use this value in mathematical formulas, we need to convert from string to float. If the value in the Appraisal Value column ends with \u201ck\u201d, we first use rstrip() to strip \u201ck\u201d from the string in the Appraisal Value column. Now that we only have numeric characters, we use float() to convert our string to a float. Because K represents 1000 and we have removed this from the value, we multiply our float by 1000. If the value in the Appraisal Value column does not end with \u201ck\u201d, and only contains numeric characters, we can simply use float() to convert our string to a float.","title":"Example converting a string to a float"},{"location":"formula-cheat-sheet/#troubleshooting","text":"if you are trying to use different columns with numeric values in a mathematical formula but seeing an error, check the column types for each of the columns used in the formula. All need to be of type Numeric . float() is only needed when dealing with alpha-numeric values like we see in the example . TypeError: can\u2019t multiply sequence by non-int of type \u2018float\u2019 This error occurs when a formula attempts to multiply values from multiple columns, at least one of which is not a Numeric type column. In the screenshot below, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for /: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to divide values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the \u2018# of Payments\u2019 column is a Choice column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for +: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to add values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Tax column is a Text column. When we change the column type to Numeric , the error is resolved. TypeError: unsupported operand type(s) for -: \u2018float\u2019 and \u2018str\u2019 This error occurs when a formula attempts to subtract values from multiple columns, at least one of which is not a Numeric type column. In the example seen above, the Discount column is a Text column. When we change the column type to Numeric , the error is resolved.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#rounding-numbers","text":"Specify the number of decimal places to give in a result using the ROUND() function. If Average Temperature is equal to 46.5 , ROUND($Average_Temperature) would return 47","title":"Rounding Numbers"},{"location":"formula-cheat-sheet/#example-of-rounding-numbers","text":"Payroll template The formula used in the Payment column is: ROUND($Hours*$Per_Hour, 2) The ROUND() function follows the format ROUND(value, places) which will round the given value to the number of places specified. Our formula finds the value for $Hours*$Per_Hour then rounds this value to 2 decimal places. Mixing Products The formula used in the Rounded Value column is: mix_list_str = $Mix_Product.Lt_per_100_Lt mix_list_float = [float(i) for i in mix_list_str] x = [Lt * $Water/100 for Lt in mix_list_float] round_x = [ROUND(num, 2) for num in x] l = $Mix_Product.Product ' '.join('{} {}'.format(first, second) for first, second in zip(l, round_x)) Let\u2019s break this down. $Mix_Product represents the Mix Product column, a reference list column that pulls data from the Product column of Table1. We can use this column as a link to Table1 to pull other data. $Mix_Product.Lt_per_100_Lt uses the reference list column, Mix Product, to pull values from the Lt per 100 Lt column of Table1 for the products listed in the Mix Product column of Table2 then assigns this list of values to the variable mix_list_str . This is the same formula used in the Lt per 100 Lt column of Table2 so you can see the value it returns in row 1 of Table2. It returns a list: ['0.2355', '1.2579'] . This list is evaluated as a string rather than numerical values. We need to convert each value in this list to a float. In our next formula, [float(i) for i in mix_list_str] , we iterate through the list that was assigned in the first equation to mix_list_str and convert each value to a floating-point number. We want to convert to a float rather than integer because not all values are whole numbers and contain decimals. i is a variable representing each value. So each value in mix_list_str is evaluated in the equation float(i) . float(0.2355) converts 0.2355 to a float and float(1.2579) converts 1.2579 to a float. Now, we assign our list of floats to the variable mix_list_float . We can now use our float values in a mathematical equation. Once again, we iterate through the list that was assigned to the variable mix_list_float . In our equation [Lt * $Water/100 for Lt in mix_list_float] , Lt represents each value in mix_list_float and $Water represents the value found in the Water column which is 1000 . We evaluate the equation Lt * 1000/100 when Lt = 0.2355 and Lt = 1.2579 which returns the list [2.355, 12.579] . We assign this list to the variable x . To round the values in x to two decimal places, we need to evaluate the equation ROUND(num, 2) where num represents each value in our list and 2 specifies the number of decimal places we want to round to. This returns the list [2.36, 12.58] which we assign to the variable round_x. In the first equation, we used our reference list column, Mix Product, as our link to Table1 in order to pull data from Table1 into Table2. We use this method again in $Mix_Product.Product to pull data from the Product column of Table1. This returns a list of products; [Prod A, Prod B] . We assign this list to the variable l . Finally, we use the join() method to combine our two lists. ' ' is our starting (empty) string. We use Python\u2019s format method to format our string. {} is a placeholder for each variable listed in .format() . Last, we use Python\u2019s zip() function to pair the first values from each list together and then pair the second values in each list together. l is assigned as our first list and round_x is assigned as our second list. l = [Prod A, Prod B] and round_x = [2.36, 12.58] . Zipping our lists into '{} {}'.format(first, second) gives us Prod A 2.36 in our first iteration and Prod B 12.58 in our second iteration. Our final return value is Prod A 2.36 Prod B 12.58 .","title":"Example of rounding numbers"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros","text":"Allows you to specify the minimum number of digits returned in a numerical column by adding leading zeros. If x = 5, str(x).zfill(3) or '{:0>3}'.format(x) would return 005 .","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#formatting-numbers-with-leading-zeros_1","text":"Community Example: Using Row ID The formula used in the 5-digit ID column of the ID Examples table is: 'TCH{:0>5}'.format($id) '{:0>5}'.format($id) takes the unique row ID and formats it to be a minimum of 5 digits. We then add this to our string \"TCH\" to get our final value. If the $id is longer than 5 digits, the formula returns the string unmodified. We can do the same thing using the str.zfill() method. The formula used in the zfill Method column of the ID Examples table is: str($id).zfill(5) str($id) converts the row ID to a string. .zfill(x) returns a copy of the string with leading zeros to make a string of length x . In our example, it adds leading zeros to make the string 5 characters in length. Again, if the $id is longer than 5 digits, the formula returns the string unmodified.","title":"Formatting numbers with leading zeros"},{"location":"formula-cheat-sheet/#troubleshooting-errors_1","text":"#TypeError : can only concatenate str (not \u201cint\u201d) to str If you mean to combine a string and a numerical value, be sure to convert it to string using str() .","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#working-with-strings","text":"","title":"Working with Strings"},{"location":"formula-cheat-sheet/#combining-text-from-multiple-columns","text":"Method 1: If you have a First Name column and a Last Name column, you can combine these columns to have a Full Name column. If First Name is George and Last Name is Washington , $First_Name + \" \" + $Last_Name would return George Washington . Method 2: If you have additional formatting, an easier way to do this would be using Python\u2019s String format() method . The format() method formats the specified value(s) and inserts them in place of the placeholder, {} . Using the same example as above, our formula would be \"{} {}\".format($First_Name, $Last_Name) . Note: You can click on columns to insert them into your formulas, rather than typing them in.","title":"Combining Text From Multiple Columns"},{"location":"formula-cheat-sheet/#examples-using-method-1","text":"Class Enrollment template The formula used in the Full Name column of the Students table is: $Last_Name + \", \" + $First_Name Here, we are combining the value found in the Last Name column with a comma followed by a space followed by the value from the First Name column. When adding any extra characters or spaces, place these between double quotes, as we did in the example with \", \" . An alternative combination of these columns for Full Name could be $First_Name + \" \" + $Last_Name . For the example in row 1, First Name is Brockie and Last Name is Raddon so the value returned would be Brockie Raddon . Inventory Manager template The formula used in the SKU column of the All Products table is: $Brand.Brand_Code+\"-\"+$Color.Code+\"-\"+$Size Brand is a reference column that pulls data from the Name Brand column of the Add Products table. We use this reference column in $Brand.Brand_Code to pull data from the Brand Code column of the Add Products table. Color is a reference column that pulls data from the Color column of the Color table. We use this reference column in $Color.Code to pull data from the Code column of the Color table. Each of the values found in $Brand.Brand_Code and $Color.Code are combined with the value in the Size column with a - between each of the three values to make up the SKU.","title":"Examples using Method 1"},{"location":"formula-cheat-sheet/#examples-using-method-2","text":"Tracking Time + Invoicing template The formula used in the Project Name column of the Projects table is: \"{}: {}\".format($Client.Name, $Name) Let\u2019s break this down. Everything between double quotes \" is our string. The curly brackets {} are placeholders for the values found using .format() which is Python\u2019s string format() method. The first set of curly brackets are replaced with the value found in $Client.Name . Client is a reference column that pulls data for a specific record from the Clients table. $Client.Name is using our reference column, Client to pull data from the Name column of the Clients table. The second set of curly brackets are replaced with the value found in the Name column of this table. Although the Client column shows the value that we want, we can\u2019t use $Client like we did $Name . This is because the Client column is a reference column. It is referencing the entire record but uses the value from the Name column of the Clients table as a visual representation of that record. Under the column configuration panel on the right hand side, we can change what column value we see for the record. In the screenshot below, \u2018Show Column\u2019 was changed from Name to Email. It doesn\u2019t change the data, it just changes the label on that data in the Client column. It\u2019s still pointing to the same record but now shows a different label. $Client.Name pulls the Name for the record that is referenced in the Client column, regardless of the label we see. Custom Product Builder template The formula used in the All Components column of the CONTRACT_BUILDER Card is: '\\n'.join(sorted( \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() )) We are using the join() method , sorted() function and format() method method all in one! '\\n'.join() adds a new line between each item in the list. sorted() sorts the items in the list alphabetically. This leaves us with the following: \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) for (comp, quantity) in $Components.items() We\u2019ll work through this backwards. First, we need to take a look at the Components column which is a hidden column in the All Contracts table. This column is a list of components and their associated quantities for the contract. In the for loop, we assign each item in the list of components two variables, comp and quantity. For Components[3]: 6.0 , comp = Components[3] and quantity = 6.0 . Components[#] specifies a Component in the Components table by Row ID. Components[3] is the component assigned 3 as it\u2019s row id. Now, we run each item from the list above through the equation \"{} \u2014 {:g} {}\".format(comp.Component, quantity, comp.Unit) . comp.Component replaces the first set of {} . comp is the variable with our component ID so comp.Component finds the value in the Component column associated with that row ID. For Components[3] , comp.Component is Nozzle. quantity replaces the second set of {} . Again, the quantity is the second variable in our list. For Components[3]: 6.0 , quantity is 6.0 . Our second set of {} are not empty. They include :g *. This converts the value to a floating-point number. comp.Unit replaces the last set of {} . comp is the variable with our component ID so comp.Unit finds the value in the Unit column associated with that row ID. For Components[3] , comp.Unit is None . *Note that {:g} formats floating point numbers in a particular way that omit decimals when they aren\u2019t needed. There are many options available within placeholders for formatting numbers, dates, etc. Learn more about placeholders here . Email Contacts template The formula used in the Body column of the Advanced Compose table is: \"Dear %s,\\n\\nWelcome to the %s team!\" % ($Contact_Name_as_Plaintext, $Team) This technique uses the % operator instead of the format() method. Format specifiers begin with % followed by a character that represents the data type. %s is a placeholder for a string. The first %s is replaced with the value found in the \u201cContact Name as Plaintext\u201d column which is a hidden column and the second %s is replaced by the value in the Team column. \\n adds a new line.","title":"Examples using Method 2"},{"location":"formula-cheat-sheet/#splitting-strings-of-text","text":"Split a string using Python\u2019s split() method. If Full Name is George Washington , $Full_Name.split(\" \") would return [George, Washington] .","title":"Splitting Strings of Text"},{"location":"formula-cheat-sheet/#example-of-splitting-strings-of-text","text":"Community Example: Colors The formula in the \u201cColor Reference (Just URL)\u201d column of Table 2 is: split = $Color_Reference.Color.split(\" \") return split[-1] $Color_Reference.Color uses the reference column, \u201cColor Reference\u201d to pull data from the table it is referencing, Table 1. Specifically, it pulls the value from the Color column of Table 1. Color is a text column that contains a hyperlink with a label. We only see the label in Table 1 but as you can see in the screenshot above, the value in the \u2018pink\u2019 cell is expanded to show the entire string which contains \u201cpink\u201d followed by the URL. You can also see this in the \u201cColor Reference\u201d column of Table 2. We want to get the link by itself in \u201cColor Reference (Just URL)\u201d. We can do this using Python\u2019s split() method. .split(\" \") allows us to split the string anywhere there is a space (\" \") . In the Color column, there is a label followed by a space followed by the URL. The value from the Color column is split into a list containing two items Label and URL . This list is assigned to the variable split . We want to return the last item in the list split in order to get our URL . The last item in a list always has index [-1] . return split[-1] returns the last item in the list split .","title":"Example of Splitting Strings of Text"},{"location":"formula-cheat-sheet/#direct-link-to-gmail-history-for-a-contact","text":"If you store contacts in Grist, and use Gmail to email them, you can create a formula that will open Gmail to a list of conversations with that contact. Read about it in the Community: Pull up Gmail history for a particular contact","title":"Direct Link to Gmail History for a Contact"},{"location":"formula-cheat-sheet/#troubleshooting_1","text":"Is your URL still showing after you added a label? Make sure your Column Type is Text and Cell Format is Hyperlink.","title":"Troubleshooting"},{"location":"formula-cheat-sheet/#joining-a-list-of-strings","text":"When you want to join a list of strings, you can use Python\u2019s join() method .","title":"Joining a List of Strings"},{"location":"formula-cheat-sheet/#example-of-joining-a-list","text":"Community Example: .join() Example The formula used in the Advertisement column of the 2022 Grand Openings table is: \"Coming soon to a city near you!\\n\" + \" : \".join($New_Location_s_in_2022) Here, we are joining multiple strings to create our advertisement. \"Coming soon to a city near you!\\n\" is returned almost exactly as we see it, minus the quotes \"\" and \\n at the end of the string. The quotes \"\" specify that this is a string and \\n is actually a newline character that can be used to specify a new line within a string. \" : \".join($New_Location_s_in_2022) is also a string but uses Python\u2019s join() method to join values from our choice list column, \u201cNew Locations in 2022\u201d. What we see in quotes before .join is what will separate each value in our list. In this example, each value is separated by a space, : and another space.","title":"Example of Joining a List"},{"location":"formula-cheat-sheet/#finding-duplicates","text":"You can find duplicates in a column using either conditional formatting or a helper column. Read about it in the Community: Ensure unique values or detect duplicates","title":"Finding Duplicates"},{"location":"formula-cheat-sheet/#example-of-finding-duplicates","text":"Community Example: Finding Duplicates The formula used in the Duplicate? column of the Duplicates table is: len(Duplicates.lookupRecords(Grocery_List=$Grocery_List))>1 Let\u2019s break this down, working from the inside > out. Duplicates.lookupRecords(Grocery_List=$Grocery_List) This is a lookupRecords function that follows the format of: [Table_Name].lookupRecords([A]=$[B]) Where [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. This formula looks up records in the Duplicates table where a record in the Grocery List column matches another record in the same column. len() counts the number of records in our list. Since each duplicate will match with the other, it should appear twice in our list. This is why len() > 1 . if len() > 1 , the formula is true. If len() <= 1 , the formula is false. This same formula can be used in conditional formatting. This can be seen in the \u2018Grocery List\u2019 column of the Duplicates table. If len() > 1 , our formula is true and the conditional cell color is applied to these cells. If len() <= 1 , our formula is false and the cell color is unchanged.","title":"Example of Finding Duplicates"},{"location":"formula-cheat-sheet/#using-a-records-unique-identifier-in-formulas","text":"When a record is created, it is assigned a numeric id (available as $id in formulas) that is unique within that table. You can reveal the row ID by adding a formula column where the formula is $id .","title":"Using a Record’s Unique Identifier in Formulas"},{"location":"formula-cheat-sheet/#examples-using-row-id-in-formulas","text":"You can reveal the ID with the formula $id Custom Product Builder template The formula used in the Contract No. column of the Contract Builder table is: $id + 500 Here, we are using a trigger formula to create a unique Contract Number when a record is created. Class Enrollment template The formula used in the Count column of the Classes table is: len(Enrollments.lookupRecords(Class=$id, Status=\"Confirmed\")) This is refered to as a Reverse Lookup. We can use the row id to match a record in another table where a reference column is used. LookupRecords follows the format [Table_Name].lookupRecords([A]=$[B]) . [Table_Name] is the name of the table you want to lookup data in. [A] is the column in the table being looked up (named at the beginning of the formula) and [B] is the column in the current table / the table you are entering the formula in. Lookup Records creates a list of records that match the criteria listed. len() counts how many records are in that list. Here, we are looking up records from the Enrollments table where the record called out in the Class column (our reference column) has the same row ID as the row in the table you are entering the formula. Additionally, the value in the Status column of the Enrollments table is Confirmed . We\u2019ll walk through this. The table we are looking up records in is the Enrollments table. Our criteria comes from the Class column and the Status column.The criteria for Status is straightforward; the value must be Confirmed in order to be included in our list of records. Class is a bit more complicated. As we see in the screenshot below, Class is a reference column that pulls data from the Classes table. Here, the Class column shows 2018F-Stars . A reference column points to the entire record, not just the value you see here in the Class column. Using the configuration panel on the right hand side of the screen, you can pick any column from the originating table to show. For this example, the Class column shows the value from the Class Code column of the Classes table but it points to the entire record where the class code is 2018F-Stars . As you can see in this screenshot, the Row ID for this particular record is 2 and because we are calculating the Count for the row with Row ID = 2 , it will count all records in the Enrollment table where Class shows 2018F-Stars and Status is Confirmed . Restaurant Custom Orders template The trigger formula used in the BOM # column of the Bill of Materials table is: MAX(o.BOM_ for o in Bill_Of_Materials.all if o.id != $id) + 1 First, we\u2019ll walk through the formula inside the parenthesis then work outwards. Here, o is a variable representing each record in our table. o.BOM_ represents the BOM # for each record and o.id represents the row ID for each record. This is a for loop that makes a list of the BOM # for each record in the table Bill of Materials when the record ID does not equal the ID of this row. MAX() finds the maximum BOM # in the list then + 1 to get our final value. This is a trigger formula that only applies to new records. When a new record is created, the formula finds the highest BOM # in the table then adds 1 so we have a unique BOM # for the new record.","title":"Examples Using Row ID in Formulas"},{"location":"formula-cheat-sheet/#removing-duplicates-from-a-list","text":"You can remove duplicates from a list with help from Python\u2019s set() method.","title":"Removing Duplicates From a List"},{"location":"formula-cheat-sheet/#example-of-removing-duplicates-from-a-list","text":"Community Example: Removing Duplicates From a List The formula in the All Divisions column of the Abroad Trips table is: confirmed_div = $Attending_Confirmed.Role_Division.Division pending_div = $Attending_Pending.Role_Division.Division full_list = confirmed_div + pending_div return sorted(set(full_list)) We will walk through this one line at a time. Attending-Confirmed is a Reference List column that pulls data from the EMPLOYEES table. $Attending_Confirmed.Role_Division pulls the value from the Role Division column of the EMPLOYEES table. The Role Division column in the EMPLOYEES table is a reference column itself, which points to a record in the Divisions table. Chaining allows us to specify what information we want from this record. In this case, we want the Division. We expand our formula to $Attending_Confirmed.Role_Division.Division . The Division is found for each employee listed in the Attending-Confirmed column, creating a list. We assign this list of divisions to the variable confirmed_div . Attending-Pending is also a Reference List column that pulls data from the EMPLOYEES table. $Attending_Pending.Role_Division.Division does the same as above except now we pull the division for each employee in the Attending-Pending column. We assign this list to the variable pending_div . We create a list of all divisions by adding the two lists together and assigning this combined list to the variable full_list . return sorted(set(full_list)) first uses Python\u2019s set() method to create a list with no duplicate divisions. We then use the sorted() method to return our set of divisions, sorted alphabetically. Note that the formula above can be simplified even further to: sorted( set($Attending_Confirmed.Role_Division.Division) | set($Attending_Pending.Role_Division.Division) )","title":"Example of Removing Duplicates from a List"},{"location":"formula-cheat-sheet/#setting-default-values-for-new-records","text":"You can set default values for when a new record is created and save yourself the trouble of having to fill in the same fields with the same values time after time. Read about it in the Community: Default values on the widget","title":"Setting Default Values for New Records"},{"location":"formula-cheat-sheet/#working-with-dates-and-times","text":"","title":"Working with dates and times"},{"location":"formula-cheat-sheet/#automatic-date-time-and-author-stamps","text":"You can automatically add the date or time a record was created or updated as well as who made the change.","title":"Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#examples-of-automatic-date-time-and-author-stamps","text":"Grant Application Tracker template The formula used in the Last Updated column of the Tasks table is: NOW() This is a trigger formula that triggers when a change is made to any field for this record. When a change is made, this formula runs its calculation. NOW() calculates the current time and date for the time zone selected. The formula used in the Created By column of the Tasks table is: user.Name This is a trigger formula that triggers when a new record is created. When the record is created, this formula runs its calculation. user.Name looks up the user account that is logged into Grist and returns the name associated with that account.","title":"Examples of Automatic Date, Time and Author Stamps"},{"location":"formula-cheat-sheet/#troubleshooting-errors_2","text":"If the time value in your datetime column is not calculating, check your formula. If TODAY() is used in DateTime, the time will always show 12:00am as you see below. NOW() is used for DateTime columns. TODAY() is used for Date. #AttributeError You have likely entered user.name but the formula is user.Name . Keep an eye on capitalization! #NameError You may have entered username or userName . The correct formula is user.Name . Another possibility is that this was entered in as a Formula column rather than a trigger formula column. Convert it to a trigger formula and this should resolve the problem.","title":"Troubleshooting Errors"},{"location":"formula-cheat-sheet/#filtering-data-within-a-specified-amount-of-time","text":"Using the DATEADD() function and comparision operators , you can determine if a date falls within a specific range then apply a filter.","title":"Filtering Data within a Specified Amount of Time"},{"location":"formula-cheat-sheet/#example-filtering-data-that-falls-in-1-month-range","text":"Community Example: Filtering Data Within a 1-Month Range The formula used in the Falls in 1 Month Range? column of the Interactions table is: TODAY() >= $Date >= DATEADD(TODAY(),months=-1) TODAY() returns the current date. $Date is the name of a column in our table, which is a Date type column. DATEADD(start_date, days=0, months=0, years=0, weeks=0) returns the date that is the given number of days, months, years, or weeks before or after the start_date . In this example, it returns the date that is one month prior to the start date, TODAY() . This formula is true if the date value in the Date column falls between TODAY() and our DATEADD() date which is one month ago. If the date value in the Date column does not fall between these two dates, the formula returns false. We can use this column to filter our data. If we only want to see interactions that fall within the 1 Month Range, we would filter to only include true values. If we want to see interactions that fall outside of the 1 Month Range, we would filter to only include false values.","title":"Example Filtering Data that ‘Falls in 1 Month Range`"},{"location":"formula-cheat-sheet/#troubleshooting-errors_3","text":"#TypeError : Because $Date is a Date type column, TODAY() must be used in formulas comparing dates. NOW() is a DateTime formula that should only be used with other DateTime values. For example, if the $Date column was a DateTime type column, NOW() would need to be used rather than TODAY() because it includes the time component. NOW() is date and time. TODAY() is only date.","title":"Troubleshooting Errors"},{"location":"ai-assistant/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AI Formula Assistant # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used . How To Use the AI Assistant # Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula. AI Assistant for Self-hosters # For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist . Pricing for AI Assistant # Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person) Best Practices # It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you. Data Use Policy # Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"AI Formula Assistant"},{"location":"ai-assistant/#ai-formula-assistant","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. The assistant only does one thing, write formulas in response to plain language queries. When asking the assistant a question there\u2019s no need to specify column IDs or explain the structure of your data. When you submit a question to the assistant, Grist sends your question and your data\u2019s schema (in part or whole) to OpenAI so that the assistant may better understand your document. You can view your data\u2019s schema in code view. No data is shared with OpenAI unless a user submits a question to the assistant. Learn more about how data is used .","title":"AI Formula Assistant"},{"location":"ai-assistant/#how-to-use-the-ai-assistant","text":"Create a formula column and click either the expand icon or the \u201cuse AI Assistant\u201d link text in the cell. This will open an expanded formula editor with the AI Assistant chat below it. Simply describe what you want the formula to do. Here are some tips. When you apply a suggested formula, you will see the formula previewed in the column. If you are happy with the formula, click \u201cSave\u201d. Otherwise click \u201cCancel\u201d to discard changes to the formula.","title":"How To Use the AI Assistant"},{"location":"ai-assistant/#ai-assistant-for-self-hosters","text":"For self-hosters looking to connect their Grist instance, set the AI Assistant-related environment variables . The above variables also enable the use of the LLaMA family of self-hostable models via llama2-cpp-python . Learn more about self-managing Grist .","title":"AI Assistant for Self-hosters"},{"location":"ai-assistant/#pricing-for-ai-assistant","text":"Free personal and free team plans have 100 AI Assistant credits (or requests). For free team sites, that applies to the whole team. Pro plans include 100 AI Assistant credits per month. The credits automatically top up to 100 every billing cycle. Credits apply to the whole team. It costs one credit per chat message sent. If you need more credits, there are two upgrade options: 500 monthly credits for $10 per month (per team, not per person) 2,000 monthly credits for $29 per month (per team, not per person)","title":"Pricing for AI Assistant"},{"location":"ai-assistant/#best-practices","text":"It helps to understand how formulas work in Grist, especially when compared to traditional spreadsheets. In Grist, a single formula applies to a whole column. If you\u2019ve worked with spreadsheets before, you may be surprised that you don\u2019t need to specify row numbers, like B1 * C1 . If you\u2019re new to Grist, you might want to try something simple without the assistant to see how formulas behave, e.g. $Price * $Tax . Setting a column header before submitting a question improves results. For example, \u201cNet Profit\u201d provides more context than a column labeled \u201cD\u201d. Set the column type of the formula column before asking the assistant for help. This helps the assistant guess what type of values you\u2019d like returned. Think about the type of value you expect to see in the formula, and frame the question with that in mind. For example, if you want a formula that returns True or False in a Toggle column , ask a yes-or-no question. Otherwise, the assistant may suggest a formula that lists all rows where something is true (and is technically correct), but you were hoping for a simple true or false per row. The assistant is conversational. If you are not satisfied with a suggested formula, explain to the assistant where the formula fell short and ask it to make a change. Sometimes the assistant gets stuck on a bad idea. If it keeps insisting on a particular formula method, consider clearing the conversation and starting over. Click the three dot menu in the AI Assistant header to clear a conversation. Remember that Grist formulas apply to the whole column. If you want sums of rows, you need a summary table . You may add more formulas to summary tables, and ask the assistant to help you.","title":"Best Practices"},{"location":"ai-assistant/#data-use-policy","text":"Your query and document schema are sent to OpenAI . Grist\u2019s AI Formula Assistant uses the gpt-3.5-turbo model, aka ChatGPT. OpenAI\u2019s Privacy Policy describes how OpenAI handles your data. OpenAI\u2019s Content Policy , Usage Policies and Sharing and Publication Policy describe how the Grist AI Assistant and its results may be used and shared. Those who violate OpenAI\u2019s policies may lose access to Grist\u2019s AI assistant. Certain Grist Labs employees may also examine logs of assistant requests (questions and document schema) to learn what is working and what is not, in order to provide a better service.","title":"Data Use Policy"},{"location":"teams/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Teams # Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others. Understanding Personal Sites # Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site Billing Account # If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Creating team sites"},{"location":"teams/#teams","text":"Team sites are meant for regular collaboration by groups. They store documents at a subdomain of your choice such as your-team.getgrist.com . Team sites may be on a free or a paid plan. The difference between the Free plan and paid plans is the document limits. Documents on a Free plan can have up to 5,000 rows, and include automatic backups for 30 days. While personal sites are only available with the Free plan, team sites may be upgraded to Pro or Business plans. On paid plans, the limits are much higher. See the pricing page for more details. If you have a team site but aren\u2019t seeing it, click in the top-left corner to open a drop-down menu of sites to which you have access. Opening the user menu under your profile icon will also list all sites to which you have access. Not seeing your team site? Try selecting the \u2018Add account\u2019 option in the user menu. It\u2019s possible the team site might be linked to a different email address. A fresh team site will look something like this, ready to be filled with Grist documents and shared with your team. If a colleague has shared a team site with you, you\u2019ll see the same thing, but depending on the role they chose for you, some options may be inactive. If you need those options, ask your colleague to change your role. For a team site in which you are an Owner or Editor, you can create documents or workspaces . When your role is an Owner, you can immediately start sharing the site with others.","title":"Teams"},{"location":"teams/#understanding-personal-sites","text":"Sites that begin with the \u2018@\u2019 symbol are personal sites. All Grist accounts have a personal site. Your personal site is named using your name, and is always available at https://docs.getgrist.com . That\u2019s also where you will find personal documents shared with you by others. Each document in a personal site may be shared with up to 2 Guests for free. a personal site a team site","title":"Understanding Personal Sites"},{"location":"teams/#billing-account","text":"If you created a team site on the Pro or Business plan, or were added to it as a Billing Manager, then you may manage billing information, and edit your team site\u2019s name and subdomain from the billing account page. Open the user menu and click on \u2018Billing Account\u2019 to open a menu that looks like this. Click on the top \u2018Change\u2019 icon to edit your team site name or subdomain. Note, if you change the subdomain, then any links pointing to your site or documents within your site will need updating. Team sites can be deleted from the \u2018Billing Summary\u2019 page by clicking \u2018Delete team site\u2019. Please note that this action cannot be undone and this will remove your site including all documents. Be sure you have created a backup of important data beforehand.","title":"Billing Account"},{"location":"team-sharing/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Team Sharing # We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents . Roles # There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings. Billing Permissions # None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019. Removing Team Members # To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Sharing team sites"},{"location":"team-sharing/#team-sharing","text":"We saw how to share individual documents with other users in the Sharing article. Team sites give your further control, allowing you to share a team site\u2019s collections of documents in whole or in part with others. You may further group documents into workspaces and edit permissions at the workspace-level as well. To share a team site, click on the \u2018Manage Team\u2019 button in the top right of your team site homepage. You can now invite people just as you did for individual documents in Sharing . Just as for documents, you can invite people as viewers, editors, or owners of the team site. All documents within the site will be accessible to those people, unless you turn off the \u2018Inherit access\u2019 sharing option for individual workspaces or documents .","title":"Team Sharing"},{"location":"team-sharing/#roles","text":"There are three primary roles supported by Grist for team sites: Viewer : allows a user to view the site but not make any changes to it. A viewer will be allowed to view all workspaces and documents within the site unless otherwise specified. Editor : allows a user to view or make changes to the site and all its workspaces and documents (unless otherwise specified). However, the sharing settings for the site, its workspaces, and its documents cannot be changed by an editor. Owner : gives a user complete permissions to the site\u2019s workspaces and documents, including their sharing settings.","title":"Roles"},{"location":"team-sharing/#billing-permissions","text":"None of these roles give access to billing information or management. Billing plan managers can be added via the \u2018Billing Account\u2019 option. Open the user menu under your user icon, and select \u2018Billing Account\u2019.","title":"Billing Permissions"},{"location":"team-sharing/#removing-team-members","text":"To remove a user from your team, click \u2018Manage Team\u2019 at the upper right of the page then click the delete icon to the right of their name. You must click the confirm button to save the change. When users are removed from your team, your monthly bill will be reduced, prorated for the time remaining in the billing cycle.","title":"Removing Team Members"},{"location":"access-rules/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access rules # Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need. Default rules # To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go. Lock down structure # By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited. Make a private table # To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied: Seed Rules # When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules. Restrict access to columns # We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon : View as another user # A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload. User attribute tables # If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed. Row-level access control # In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how. Checking new values # Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage: Link keys # Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more. Access rule conditions # Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example. Access rule permissions # A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access. Access rule memos # When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records Access rule examples # Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Intro to access rules"},{"location":"access-rules/#access-rules","text":"Every Grist document can be shared with others using the Manage Users option in the sharing menu ( ). Users can be invited as Viewers, Editors, or Owners (see Sharing a document for a refresher on those roles), or a document can be shared publicly with view or edit permissions. Sometimes, you need more nuance about who can see or edit individual parts of a document. Access rules give us that power. Only the owners of a document can edit its access rules. When a document is loaded, owners see a tool called Access Rules in the left side bar. Click on that to view and edit access rules. The rules are also accessible via the Manage Users option of the sharing menu with the Open Access Rules button (available only to document Owners). Suppose we run a small business sourcing and delivering unusual objects, organized using a document with two tables, Orders and Financials . Now we are taking on more employees, and want to share the document with them while limiting their access to just what they need.","title":"Access rules"},{"location":"access-rules/#default-rules","text":"To see the access rules for a document, visit its access rules page by clicking Access Rules in the left sidebar. When no custom rules have been created yet, the access rules page contains the Default Rules for our document: These rules say, in summary, that Owners and Editors can do anything within the document, that Viewers can only read the document, and everyone else is forbidden all access. These rules cannot be modified, but they can be overridden. To understand whether a group of rules allows a certain permission ( Read, Update, Create, Delete, or Structure ), read the rules from top to bottom, and find the first applicable rule that allows (green) or denies (red) that permission. We\u2019ll see plenty of examples as we go.","title":"Default rules"},{"location":"access-rules/#lock-down-structure","text":"By default Owners and Editors are equally powerful within a document, with the ability to create or delete tables or columns, write formulas, reorganize pages, and so on. Suppose we want only the original Owners of the document to be allowed to change its structure, as we plan to invite other specialized collaborators as Editors. To do this, uncheck the box for the first rule listed under \u2018Special Rules\u2019 to disallow editors from editing structure. Once we\u2019ve made changes, the SAVE button becomes an inviting green. We click SAVE for the rule to take effect. Important. This is an important first step for any document where you intend to block any access to Editors. Without denying them the structure permission ( S ), anyone with edit access will be able to create or change formulas. Since formula calculations are not limited by access control rules, a determined user could use them to retrieve any data from a document. To protect against that, deny structure permission to users whose access should be limited.","title":"Lock down structure"},{"location":"access-rules/#make-a-private-table","text":"To ensure that only Owners can access a table, such as the Financials table in our example, we click Add Table Rules and select the table name, Financials . This creates a new empty group of rules called Rules for table Financials . Then we add a condition for any user who is not an Owner ( user.Access != OWNER ), with all permissions denied. Selecting Deny All from the drop-down beside R U C D is a fast way to set all permissions to denied, or you can click each permission individually to turn them red. R is Read, U is Update, C is Create, and D is Delete (see Access rule permissions ). Structure ( S ) permissions are not available at the table level. Once you are done, click SAVE . Now we could go ahead and share the document with a team member specialized in deliveries, for example. We share the document with them as an Editor so that the restrictions we\u2019ve set up apply to them. They won\u2019t see the Financials table in the left side bar, and attempts to open it will be denied:","title":"Make a private table"},{"location":"access-rules/#seed-rules","text":"When writing access rules for specific tables, it is fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. To automatically add a set of rules to all new table rules, you can write \u201cseed rules.\u201d There is a checkbox above default rules that makes the common case easier with one click. Click it to write a seed rule that will automatically grant owners full access whenever table rules are added. Click the > icon to uncollapse the seed rules table to modify seed rules.","title":"Seed Rules"},{"location":"access-rules/#restrict-access-to-columns","text":"We can restrict a collaborator\u2019s access to columns. In our eample, we might wish to give a delivery specialist more limited access to the Orders table. Perhaps they don\u2019t need to see an Email column, or a Piece column with details of what is in the parcel. Click Add Table Rules and select Orders to create a rule group for the Orders table. Now, in the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule : In the Columns area we have a new [Add Column] dropdown to add all the columns to which we want the rule to apply (in our case Email and Piece ). For the condition, we could use user.Email == 'kiwi@getgrist.com' . This checks for the email address of Kimberly, our fictional delivery specialist; we could also check by name or a numeric ID. We turn all available permissions off for this user on these columns: Now that the rules are ready, click Save . If we have another employee who specializes in sourcing objects, and who needs to see a different set of columns, we can do that. For example here we add a rule to withhold Address and Phone columns from user Charon :","title":"Restrict access to columns"},{"location":"access-rules/#view-as-another-user","text":"A convenient way to check if access rules work as expected is with the View As feature, available in the View As dropdown. This allows an Owner to open the document as if they were one of the people it is shared with, to see what their colleague would see. The Owner does not \u201cbecome\u201d that colleague - any changes they make will be recorded as coming from themselves and not the colleague - but they do see the document from the colleague\u2019s perspective. In our example, we could select Kiwi, and the document reopens, with a large banner stating that we are viewing it as Kiwi. The Piece and Email columns are missing, and the Financials table is surpressed: You can also check in Raw Data to confirm only the expected tables, columns, and rows are exposed. When satisfied that everything looks as expected, we click the green View as Yourself button to close this preview, and the document will reload.","title":"View as another user"},{"location":"access-rules/#user-attribute-tables","text":"If we are successful and hire many sourcing and delivery people, then adding them one by one to rules would be tedious. One solution is to use \u201cuser attribute tables.\u201d You can add a table to your document that classifies users as you like, and then use those classes in your access rules. For example, we can make a table called Team , and give it two columns, Email and Role , where Role is a choice between Sourcing and Delivery . Now we can tell Grist to make information from this table available for access rules, by clicking on Add User Attributes . Give the attribute any name you like (this will be how we refer to it in formulas), such as Team . Pick the table to read ( Team also in this case). Give a user property to match against rows in this table - in our case we\u2019ll use user.Email . And the column to match against, Email . Save that. Now we can update our rules to be more general. We find with autocomplete that we have a new user.Team variable available in condtions. It makes columns from the Team available, such as user.Team.Role . Now we can check if the user has a particular role, and apply the permissions that go with that: Great! Doing a spot check, Charon sees the expected columns for someone in Sourcing. And if we recruit someone else to work with them, we can just add them in the Team table, no rule changes needed.","title":"User attribute tables"},{"location":"access-rules/#row-level-access-control","text":"In our example, as orders are processed, they move from sourcing to delivery phases. So there\u2019s really no need for the two groups to see all the orders at once. Let\u2019s add a column called Stage that can be set to Sourcing or Delivery , so that we can update access rules to show only the relevant orders. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Table-wide Rule to add a rule that isn\u2019t limited to specific columns. Let\u2019s deny access to all rows for non-Owners as a starting point, then add back in the ones we want. We can do that with the condition user.Access != OWNER with Deny All permissions. Then, we add another default rule by clicking + , and add the condition user.Team.Role == rec.Stage . The rec variable allows us to express rules that depend on the content within a particular record. Here, we check if the Stage column of a record matches the user\u2019s role. If it is, we allow R Read access: Here\u2019s how the table looks now as Kimberly (doing deliveries): And here\u2019s how the table looks as Charon (doing sourcing): Kimberly and Charon now have read-only access to the table. Owners still have full write access to all rows and columns. Understanding reference columns in access rules You can limit the data team members access to just those rows pertinent to their work. One way to do so is to relate all records in all tables to their respective team members. For example, leads and sales records can reference the sales rep responsible for those records. This quick video explains how.","title":"Row-level access control"},{"location":"access-rules/#checking-new-values","text":"Access rules can be used to permit only certain changes to the document. Suppose we want Delivery people to be able to change Stage from Delivery to Done , without giving them the arbitrary rights to edit that column. We can grant them that exceptional right as follows. In the Rules for table Orders group, click the three-dot icon (\u2026), and select Add Column Rule . Set [Add Column] to Stage , and mark the U/Update permission to be granted. For the condition, use this: (user.Team.Role == 'Delivery' and rec.Stage == 'Delivery' and newRec.Stage == 'Done') This checks if the user has the Delivery role, and the record is in the Delivery stage, and that the user is trying to change the Stage to Done . The newRec variable is a variant of rec available when the user is proposing to change a record, with rec containing its state before the change, and newRec its state after the proposed change. Now, if we view the table as Kiwi, and try to change a Stage to Sourcing , we are denied: If we change a Stage to Done , it works, and the record disappears from view since it is no longer in the Delivery stage:","title":"Checking new values"},{"location":"access-rules/#link-keys","text":"Sometimes it is useful to give access to a specific small slice of the document, for example a single row of a table. Grist offers a feature called \u201clink keys\u201d that can help with that. Any parameters in a Grist document URL that end in an underscore are made available to access rules in a user.LinkKey variable. So for example if a document URL ends in ....?Token_=xx-xx-xx-xx&Flavor_=vanilla , then user.LinkKey.Token will be set to xx-xx-xx-xx and user.LinkKey.Flavor to vanilla . Let\u2019s work through an example to see how that can be helpful. Suppose we have a table of Orders and we\u2019d like to occasionally share information about a single order with someone. To do that with link keys, we need some kind of hard-to-guess code for each order, which can be used to access it. Grist has a UUID() function that gives a unique, random, and hard-to-guess identifier, so let\u2019s add a UUID column with formula =UUID() : In fact we want UUID() to be called just once per order, when we create it, and never recomputed (because then it would change). So in the right sidebar we convert the formula column to a data column, freezing its values: This converts our formula to a trigger formula. We set the formula to apply to new records: At this point we have a solid hard-to-guess code for each order in the UUID column, that will be created as we add new orders. It can be handy at this point to construct links to the document with that code embedded in them. Grist has a helper for this called SELF_HYPERLINK . To add a link key called , just use this function with a LinkKey_ argument. In our case, we pass LinkKey_UUID=$UUID to embed the value of the UUID column into the URL. We also set label=$Ref to control the text label of the link in the spreadsheet. To show the link, we set the column type to Text and set the HyperLink option: Once we have these links, we can tidy up a little by hiding the UUID and Ref columns (see Column operations for a refresher on how to do this): The links don\u2019t do anything special yet, but we have everything we need to make that happen now. Here is an example of access rules to allow anyone with a UUID in their URL to read any order with a matching UUID (otherwise only owners can read orders in this case): And here is what a non-owner now sees, with the UUID of the first order in their URL: This is just the beginning of the possibilities. Link keys can give access to multiple rows across many tables. They can be used in User attribute tables . And the data they give access to can be within tables, cards, card lists, charts, and custom widgets. Check out another example to deepen your understanding of link keys even more.","title":"Link keys"},{"location":"access-rules/#access-rule-conditions","text":"Access rule conditions contain a formula expressing when the rule should apply. A blank condition will always apply. When a condition applies for an action, the permissions associated with the condition are set to allowed or denied for that action if no earlier rule in the same group has yet set them. When a condition does not apply, no permissions are set by that rule, but other rules could set them. Formulas are written in a restricted subset of Python. Variables that may be available in access rules are user , rec , and newRec . The user variable contains the following members: user.Access : one of owners , editors , or viewers , giving how the document was shared with the user (see Sharing a document ). user.Email : the email address of the user (or anon@getgrist.com for users who are not logged in). user.UserID : a numeric ID that is associated with the user. user.Name : the user\u2019s name (or Anonymous if unavailable). user.LinkKey : an object with any access control URL parameters. Access control URL parameters end in an underscore (which is then stripped). Only available in the web client, not the API. user.SessionID : a unique string assigned to anonymous users for the duration of that user\u2019s session. For logged in users, user.SessionID is always \"u\" + the user\u2019s numeric id. For an example of using the user variable, read Default rules . The rec variable contains the state of an individual record/row, for conditions that need to take that into account. When it is used, that rule becomes row-specific. That allows, for example, to make certain rows visible only to certain users, or to prohibit modification of certain rows by certain users. For an example of using the rec variable, read Row-level access control . The newRec variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. For an example of using the newRec variable, read Checking new values . Supported operations in condition formalas are currently: and , or , + , - , * , / , % , == , != , < , <= , > , >= , is , is not , in , not in . Supported variables are: user , rec , newRec with their members accessed with . . Strings, numbers, and lists are also supported. If an operation you need is not available, consider whether you can do part of the work in a formula in the table itself (see Access rule memos for an example). Comments are allowed, using # or \"\"\" . If there is a comment in a rule, then the first comment in a rule that results in a denial of an action will be reported to the user as a tip for why the action was not permitted. See Access rule memos for an example.","title":"Access rule conditions"},{"location":"access-rules/#access-rule-permissions","text":"A permission controls whether a user can perform a particalar kind of action. Grist access rules currently deal with 5 kinds of action, which are given single letter acronyms for convenience: R - permission to read cells. U - permission to update cells. C - permission to create rows. D - permission to delete rows. S - permission to change table structure. The S structure permission is available in the default access rule group. Column rules lack the C create and D delete permissions, which should be handled in default table rules. Note: The S permission is very powerful. It allows writing formulas, which can access any data in the document regardless of rules. Since the S permission is on by default for Editors and Owners, any such user would be able to edit a formula and so retrieve any data. In other words, having the S permission makes it possible to circumvent other rules that prevent access to data. For this reason, turning it off \u2013 as described above in Locking down structure \u2013 is an important first step in limiting data access.","title":"Access rule permissions"},{"location":"access-rules/#access-rule-memos","text":"When a user receives an error message denying them access because of a rule, it can be helpful to give specific details that will help them understand the problem. You can do this by adding a memo for the condition. First, click the memo icon to the right of your condition. Type the error message you wish to display into the entry box. Be sure to save your changes. When the rule blocks a user from performing an action, the memo will appear as a notification. For an explanation of how this particular Access Rule works, see Access Rules to Restrict Duplicate Records","title":"Access rule memos"},{"location":"access-rules/#access-rule-examples","text":"Along with the extended example of using access rules in this section, we will collect complete examples of access rule templates and guides here. Lead lists : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Account-based Sales Team : Sales CRM with deals and contacts assigned to sales reps. Reps can only see their own contacts and deals, but managers can see everything. Public Giveaway : A public giveaway organizer that uses access rules to enforce giveaway rules without requiring claimants to log into Grist. Simple Poll : A simple poll managed in Grist with access rules to limit one response per visitor. Crowdsourced List : Publicly crowdsourced list with access rules to empower moderators to edit almost anything, but limit visitors to only making and editing their own contributions. Time Sheets : Template to capture contractor timesheets. Access rules permit contractors to view only their historical time sheets, and edit only the active month. Project Management : Track tasks by event and flag tasks at risk. Access rules limit permissions by department, and expand managers\u2019 permissions.","title":"Access rule examples"},{"location":"rest-api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Usage # Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login. Authentication # To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com . Usage # To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"REST API usage"},{"location":"rest-api/#grist-api-usage","text":"Grist has an API for manipulating documents, workspaces, and team sites. API Reference shows documentation of all available endpoints. Interactive API Console allows you to make API calls using your Grist login.","title":"Grist API Usage"},{"location":"rest-api/#authentication","text":"To access the Grist API, you\u2019ll need an API key. An API key is owned by a single user, and has the same permissions as that user. To enable API access for yourself, visit your Profile Settings . You can always find this page by clicking your profile picture or initial on the top right of the screen to open the account menu. Then select the \u201cProfile Settings\u201d option: This shows a dialog with all of your profile setting options. Scroll down to the \u201cAPI\u201d section. Click on the \u201cCreate\u201d button to create an API Key. You can now copy this key for use when making API calls. To be clear, copy the key in your profile settings, not the key in the above screenshot, which isn\u2019t a real one. You can revoke your API key by clicking \u201cRemove\u201d from Profile Settings at any time. You\u2019ll then have the option to create a new one if you wish. To test your api key, try this from the command-line (substituting your api key): curl -H \"Authorization: Bearer \" https://docs.getgrist.com/api/orgs This should return a list of organizations, which is what the API calls team sites and your personal site. Your personal site is accessible at docs.getgrist.com . Team sites are accessible at .getgrist.com .","title":"Authentication"},{"location":"rest-api/#usage","text":"To access documents on your personal site via the API, simply continue using the docs.getgrist.com domain. To access documents and workspaces on a team site, use .getgrist.com . For example, to list all the workspaces and documents you have access to on a site, do: curl -H \"Authorization: Bearer \" \\ https://.getgrist.com/api/orgs/current/workspaces When making changes via the API, and passing data via the request body, be sure to set the Content-Type header to application/json . For example, to change the name of a document, you could do: curl -XPATCH \\ -H \"Authorization: Bearer \" \\ -H \"Content-Type: application/json\" \\ -d '{\"name\": \"Lesson Plans\"}' \\ https://.getgrist.com/api/docs/ For details of the endpoints available, see our API docs or the interactive API console . There are also client libraries available: JavaScript/TypeScript client library Python client library","title":"Usage"},{"location":"api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist API Reference # REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly Grist API ( 1.0.1 ) An API for manipulating Grist sites, workspaces, and documents. Authentication ApiKey Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key. Security Scheme Type: HTTP HTTP Authorization Scheme: bearer Bearer format: Authorization: Bearer XXXXXXXXXXX orgs Team sites and personal spaces are called 'orgs' in the API. List the orgs you have access to get /orgs https://{subdomain}.getgrist.com/api /orgs This enumerates all the team sites or personal areas available. Authorizations: ApiKey Responses 200 An array of organizations Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } ] Describe an org get /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An organization Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } Modify an org patch /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"ACME Unlimited\" } Delete an org delete /orgs/{orgId} https://{subdomain}.getgrist.com/api /orgs/{orgId} Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Success 403 Access denied 404 Not found List users with access to org get /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 Users with access to org Response samples 200 Content type application/json Copy Expand all Collapse all { \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" } ] } Change who has access to org patch /orgs/{orgId}/access https://{subdomain}.getgrist.com/api /orgs/{orgId}/access Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json the changes to make delta required object ( OrgAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } workspaces Sites can be organized into groups of documents called workspaces. List workspaces and documents within an org get /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Responses 200 An org's workspaces and documents Response samples 200 Content type application/json Copy Expand all Collapse all [ { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"orgDomain\" : \"gristlabs\" } ] Create an empty workspace post /orgs/{orgId}/workspaces https://{subdomain}.getgrist.com/api /orgs/{orgId}/workspaces Authorizations: ApiKey path Parameters orgId required integer or string This can be an integer id, or a string subdomain (e.g. gristlabs ), or current if the org is implied by the domain in the url Request Body schema: application/json settings for the workspace name string Responses 200 The workspace id Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Response samples 200 Content type application/json Copy 155 Describe a workspace get /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 A workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"docs\" : [ { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null } ] , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } Modify a workspace patch /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make name string Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Retreat Docs\" } Delete a workspace delete /workspaces/{workspaceId} https://{subdomain}.getgrist.com/api /workspaces/{workspaceId} Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Success List users with access to workspace get /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Responses 200 Users with access to workspace Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to workspace patch /workspaces/{workspaceId}/access https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/access Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json the changes to make delta required object ( WorkspaceAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } docs Workspaces contain collections of Grist documents. Create an empty document post /workspaces/{workspaceId}/docs https://{subdomain}.getgrist.com/api /workspaces/{workspaceId}/docs Authorizations: ApiKey path Parameters workspaceId required integer An integer id Request Body schema: application/json settings for the document name string isPinned boolean Responses 200 The document id Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Response samples 200 Content type application/json Copy \"8b97c8db-b4df-4b34-b72c-17459e70140a\" Describe a document get /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A document's metadata Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : 145 , \"name\" : \"Project Lollipop\" , \"access\" : \"owners\" , \"isPinned\" : true , \"urlId\" : null , \"workspace\" : { \"id\" : 97 , \"name\" : \"Secret Plans\" , \"access\" : \"owners\" , \"org\" : { \"id\" : 42 , \"name\" : \"Grist Labs\" , \"domain\" : \"gristlabs\" , \"owner\" : { \"id\" : 101 , \"name\" : \"Helga Hufflepuff\" , \"picture\" : null } , \"access\" : \"owners\" , \"createdAt\" : \"2019-09-13T15:42:35.000Z\" , \"updatedAt\" : \"2019-09-13T15:42:35.000Z\" } } } Modify document metadata (but not its contents) patch /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make name string isPinned boolean Responses 200 Success Request samples Payload Content type application/json Copy { \"name\" : \"Competitive Analysis\" , \"isPinned\" : false } Delete a document delete /docs/{docId} https://{subdomain}.getgrist.com/api /docs/{docId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success Move document to another workspace. patch /docs/{docId}/move https://{subdomain}.getgrist.com/api /docs/{docId}/move Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the target workspace workspace required integer Responses 200 Success Request samples Payload Content type application/json Copy { \"workspace\" : 597 } List users with access to document get /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Users with access to document Response samples 200 Content type application/json Copy Expand all Collapse all { \"maxInheritedRole\" : \"owners\" , \"users\" : [ { \"id\" : 1 , \"name\" : \"Andrea\" , \"email\" : \"andrea@getgrist.com\" , \"access\" : \"owners\" , \"parentAccess\" : \"owners\" } ] } Change who has access to document patch /docs/{docId}/access https://{subdomain}.getgrist.com/api /docs/{docId}/access Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the changes to make delta required object ( DocAccessWrite ) Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"delta\" : { \"maxInheritedRole\" : \"owners\" , \"users\" : { \"foo@getgrist.com\" : \"owners\" , \"bar@getgrist.com\" : null } } } Content of document, as an Sqlite file get /docs/{docId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters nohistory boolean Remove document history (can significantly reduce file size) template boolean Remove all data and history but keep the structure to use the document as a template Responses 200 A document's content in Sqlite form Content of document, as an Excel file get /docs/{docId}/download/xlsx https://{subdomain}.getgrist.com/api /docs/{docId}/download/xlsx Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A document's content in Excel form Content of table, as a CSV file get /docs/{docId}/download/csv https://{subdomain}.getgrist.com/api /docs/{docId}/download/csv Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's content in CSV form The schema of a table get /docs/{docId}/download/table-schema https://{subdomain}.getgrist.com/api /docs/{docId}/download/table-schema The schema follows frictionlessdata's table-schema standard . Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters tableId required string Name of a table (normalized). header string Enum : \"colId\" \"label\" Format for headers. Labels tend to be more human-friendly while colIds are more normalized. Responses 200 A table's table-schema in JSON format. Response samples 200 Content type text/json Copy { \"name\" : \"string\" , \"title\" : \"string\" , \"path\" : \" https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&.... \" , \"format\" : \"csv\" , \"mediatype\" : \"text/csv\" , \"encoding\" : \"utf-8\" , \"dialect\" : \"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\" , \"schema\" : \"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\" } Truncate the document's action history post /docs/{docId}/states/remove https://{subdomain}.getgrist.com/api /docs/{docId}/states/remove Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json keep required integer The number of the latest history actions to keep Request samples Payload Content type application/json Copy { \"keep\" : 3 } Reload a document post /docs/{docId}/force-reload https://{subdomain}.getgrist.com/api /docs/{docId}/force-reload Closes and reopens the document, forcing the python engine to restart. Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Document reloaded successfully records Tables contain collections of records (also called rows). Fetch records from a table get /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. hidden boolean Set to true to include the hidden columns (like \"manualSort\") header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Records from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add records to a table post /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to add records required Array of objects Responses 200 IDs of records added Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 } , { \"id\" : 2 } ] } Modify records of a table patch /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the records to change, with ids records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"id\" : 2 , \"fields\" : { \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Add or update records of a table put /docs/{docId}/tables/{tableId}/records https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/records Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. onmany string Enum : \"first\" \"none\" \"all\" Which records to update if multiple records are found to match require . first - the first matching record (default) none - do not update anything all - update all matches noadd boolean Set to true to prohibit adding records. noupdate boolean Set to true to prohibit updating records. allow_empty_require boolean Set to true to allow require in the body to be empty, which will match and update all records in the table. Request Body schema: application/json The records to add or update. Instead of an id, a require object is provided, with the same structure as fields . If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in require . If so, we update it by setting the values specified for columns in fields . If not, we create a new record with a combination of the values in require and fields , with fields taking priority if the same column is specified in both. The query parameters allow for variations on this behavior. records required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"require\" : { \"pet\" : \"cat\" } , \"fields\" : { \"popularity\" : 67 } } , { \"require\" : { \"pet\" : \"dog\" } , \"fields\" : { \"popularity\" : 95 } } ] } tables Documents are structured as a collection of tables. List tables in a document get /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 The tables in a document Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } Add tables to a document post /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to add tables required Array of objects Responses 200 The table created Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" } } ] } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" } , { \"id\" : \"Places\" } ] } Modify tables of a document patch /docs/{docId}/tables https://{subdomain}.getgrist.com/api /docs/{docId}/tables Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json the tables to change, with ids tables required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"tables\" : [ { \"id\" : \"People\" , \"fields\" : { \"tableRef\" : 1 , \"onDemand\" : true } } , { \"id\" : \"Places\" , \"fields\" : { \"tableRef\" : 2 , \"onDemand\" : false } } ] } columns Tables are structured as a collection of columns. List columns in a table get /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters hidden boolean Set to true to include the hidden columns (like \"manualSort\") Responses 200 The columns in a table Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add columns to a table post /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to add columns required Array of objects Responses 200 The columns created Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } , { \"id\" : \"Order\" , \"fields\" : { \"type\" : \"Ref:Orders\" , \"visibleCol\" : 2 } } , { \"id\" : \"Formula\" , \"fields\" : { \"type\" : \"Int\" , \"formula\" : \"$A + $B\" , \"isFormula\" : true } } , { \"id\" : \"Status\" , \"fields\" : { \"type\" : \"Choice\" , \"widgetOptions\" : \"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" } , { \"id\" : \"popularity\" } ] } Modify columns of a table patch /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the columns to change, with ids columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Add or update columns of a table put /docs/{docId}/tables/{tableId}/columns https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noadd boolean Set to true to prohibit adding columns. noupdate boolean Set to true to prohibit updating columns. replaceall boolean Set to true to remove existing columns (except the hidden ones) that are not specified in the request body. Request Body schema: application/json The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created. Also note that some query parameters alter this behavior. columns required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"columns\" : [ { \"id\" : \"pet\" , \"fields\" : { \"label\" : \"Pet\" } } , { \"id\" : \"popularity\" , \"fields\" : { \"label\" : \"Popularity \u2764\" , \"type\" : \"Int\" } } ] } Delete a column of a table delete /docs/{docId}/tables/{tableId}/columns/{colId} https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/columns/{colId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables colId required string The column id (without the starting $ ) as shown in the column configuration below the label Responses 200 Success data Work with table data, using a (now deprecated) columnar format. We now recommend the records endpoints. Fetch data from a table Deprecated get /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 Cells from the table Response samples 200 Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Add rows to a table Deprecated post /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to add property name* additional property Array of objects Responses 200 IDs of rows added Request samples Payload Content type application/json Copy Expand all Collapse all { \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Modify rows of a table Deprecated patch /docs/{docId}/tables/{tableId}/data https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data Deprecated in favor of records endpoints. We have no immediate plans to remove these endpoints, but consider records a better starting point for new projects. Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables query Parameters noparse boolean Set to true to prohibit parsing strings according to the column type. Request Body schema: application/json the data to change, with ids id required Array of integers property name* additional property Array of objects Responses 200 IDs of rows modified Request samples Payload Content type application/json Copy Expand all Collapse all { \"id\" : [ 1 , 2 ] , \"pet\" : [ \"cat\" , \"dog\" ] , \"popularity\" : [ 67 , 95 ] } Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Delete rows of a table post /docs/{docId}/tables/{tableId}/data/delete https://{subdomain}.getgrist.com/api /docs/{docId}/tables/{tableId}/data/delete Authorizations: ApiKey path Parameters docId required string A string id (UUID) tableId required string normalized table name (see TABLE ID in Raw Data) or numeric row ID in _grist_Tables Request Body schema: application/json the IDs of rows to remove Array integer Responses 200 Nothing returned Request samples Payload Content type application/json Copy [ 101 , 102 , 103 ] attachments Documents may include attached files. Data records can refer to these using a column of type Attachments . List metadata of all attachments in a doc get /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters filter string Example: filter={\"pet\": [\"cat\", \"dog\"]} This is a JSON object mapping column names to arrays of allowed values. For example, to filter column pet for values cat and dog , the filter would be {\"pet\": [\"cat\", \"dog\"]} . JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is %7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D . See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for pet being either cat or dog , AND size being either tiny or outrageously small , would be {\"pet\": [\"cat\", \"dog\"], \"size\": [\"tiny\", \"outrageously small\"]} . sort string Example: sort=pet,-age Order in which to return results. If a single column name is given (e.g. pet ), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special manualSort column name. Multiple columns can be specified, separated by commas (e.g. pet,age ). For descending order, prefix a column name with a - character (e.g. pet,-age ). To include additional sorting options append them after a colon (e.g. pet,-age:naturalSort;emptyFirst,owner ). Available options are: choiceOrder , naturalSort , emptyFirst . Without the sort parameter, the order of results is unspecified. limit number Example: limit=5 Return at most this number of rows. A value of 0 is equivalent to having no limit. header Parameters X-Sort string Example: pet,-age Same as sort query parameter. X-Limit number Example: 5 Same as limit query parameter. Responses 200 List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell. Response samples 200 Content type application/json Copy Expand all Collapse all { \"records\" : [ { \"id\" : 1 , \"fields\" : { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } } ] } Upload attachments to a doc post /docs/{docId}/attachments https://{subdomain}.getgrist.com/api /docs/{docId}/attachments Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: multipart/form-data the files to add to the doc upload Array of strings < binary > Responses 200 IDs of attachments added, one per file. Response samples 200 Content type application/json Copy [ 101 , 102 , 103 ] Get the metadata for an attachment get /docs/{docId}/attachments/{attachmentId} https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment metadata Response samples 200 Content type application/json Copy { \"fileName\" : \"logo.png\" , \"fileSize\" : 12345 , \"timeUploaded\" : \"2020-02-13T12:17:19.000Z\" } Download the contents of an attachment get /docs/{docId}/attachments/{attachmentId}/download https://{subdomain}.getgrist.com/api /docs/{docId}/attachments/{attachmentId}/download Authorizations: ApiKey path Parameters docId required string A string id (UUID) attachmentId required number ( AttachmentId ) An integer ID Responses 200 Attachment contents, with suitable Content-Type. webhooks Document changes can trigger requests to URLs called webhooks. Webhooks associated with a document get /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 A list of webhooks. Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" , \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" , \"unsubscribeKey\" : \"string\" } , \"usage\" : { \"numWaiting\" : 0 , \"status\" : \"idle\" , \"updatedTime\" : 1685637500424 , \"lastSuccessTime\" : 1685637500424 , \"lastFailureTime\" : 1685637500424 , \"lastErrorMessage\" : null , \"lastHttpStatus\" : 200 , \"lastEventBatch\" : { \"size\" : 1 , \"attempts\" : 1 , \"errorMessage\" : null , \"httpStatus\" : 200 , \"status\" : \"success\" } } } ] } Create new webhooks for a document post /docs/{docId}/webhooks https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json an array of webhook settings webhooks required Array of objects Responses 200 Success Request samples Payload Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"fields\" : { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } } ] } Response samples 200 Content type application/json Copy Expand all Collapse all { \"webhooks\" : [ { \"id\" : \"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\" } ] } Modify a webhook patch /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Request Body schema: application/json the changes to make name string or null memo string or null url string < uri > enabled boolean eventTypes Array of strings isReadyColumn string or null tableId string Responses 200 Success. Request samples Payload Content type application/json Copy Expand all Collapse all { \"name\" : \"new-project-email\" , \"memo\" : \"Send an email when a project is added\" , \"url\" : \" https://example.com/webhook/123 \" , \"enabled\" : true , \"eventTypes\" : [ \"add\" , \"update\" ] , \"isReadyColumn\" : null , \"tableId\" : \"Projects\" } Remove a webhook delete /docs/{docId}/webhooks/{webhookId} https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/{webhookId} Authorizations: ApiKey path Parameters docId required string A string id (UUID) webhookId required string Responses 200 Success. Response samples 200 Content type application/json Copy { \"success\" : true } Empty a document's queue of undelivered payloads delete /docs/{docId}/webhooks/queue https://{subdomain}.getgrist.com/api /docs/{docId}/webhooks/queue Authorizations: ApiKey path Parameters docId required string A string id (UUID) Responses 200 Success. sql Sql endpoint to query data from documents. Run an SQL query against a document get /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) query Parameters q string The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string. Responses 200 The result set for the query. Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } Run an SQL query against a document, with options or parameters post /docs/{docId}/sql https://{subdomain}.getgrist.com/api /docs/{docId}/sql Authorizations: ApiKey path Parameters docId required string A string id (UUID) Request Body schema: application/json Query options sql required string The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported. args Array of numbers or strings Parameters for the query. timeout number Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced. Responses 200 The result set for the query. Request samples Payload Content type application/json Copy Expand all Collapse all { \"sql\" : \"select * from Pets where popularity >= ?\" , \"args\" : [ 50 ] , \"timeout\" : 500 } Response samples 200 Content type application/json Copy Expand all Collapse all { \"statement\" : \"select * from Pets ...\" , \"records\" : [ { \"fields\" : { \"id\" : 1 , \"pet\" : \"cat\" , \"popularity\" : 67 } } , { \"fields\" : { \"id\" : 2 , \"pet\" : \"dog\" , \"popularity\" : 95 } } ] } users Grist users. Delete a user from Grist delete /users/{userId} https://{subdomain}.getgrist.com/api /users/{userId} This action also deletes the user's personal organisation and all the workspaces and documents it contains. Currently, only the users themselves are allowed to delete their own accounts. \u26a0\ufe0f This action cannot be undone, please be cautious when using this endpoint \u26a0\ufe0f Authorizations: ApiKey path Parameters userId required integer A user id Request Body schema: application/json name required string The user's name to delete (for confirmation, to avoid deleting the wrong account). Responses 200 The account has been deleted successfully 400 The passed user name does not match the one retrieved from the database given the passed user id 403 The caller is not allowed to delete this account 404 The user is not found Request samples Payload Content type application/json Copy { \"name\" : \"John Doe\" } const __redoc_state = {\"menu\":{\"activeItemIdx\":-1},\"spec\":{\"data\":{\"info\":{\"description\":\"An API for manipulating Grist sites, workspaces, and documents.\\n\\n# Authentication\\n\\n\",\"version\":\"1.0.1\",\"title\":\"Grist API\"},\"openapi\":\"3.0.0\",\"security\":[{\"ApiKey\":[]}],\"servers\":[{\"url\":\"https://{subdomain}.getgrist.com/api\",\"variables\":{\"subdomain\":{\"description\":\"The team name, or `docs` for personal areas\",\"default\":\"docs\"}}}],\"paths\":{\"/orgs\":{\"get\":{\"operationId\":\"listOrgs\",\"tags\":[\"orgs\"],\"summary\":\"List the orgs you have access to\",\"description\":\"This enumerates all the team sites or personal areas available.\",\"responses\":{\"200\":{\"description\":\"An array of organizations\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Orgs\"}}}}}}},\"/orgs/{orgId}\":{\"get\":{\"operationId\":\"describeOrg\",\"tags\":[\"orgs\"],\"summary\":\"Describe an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An organization\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Org\"}}}}}},\"patch\":{\"operationId\":\"modifyOrg\",\"tags\":[\"orgs\"],\"summary\":\"Modify an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteOrg\",\"tags\":[\"orgs\"],\"summary\":\"Delete an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"},\"403\":{\"description\":\"Access denied\"},\"404\":{\"description\":\"Not found\"}}}},\"/orgs/{orgId}/access\":{\"get\":{\"operationId\":\"listOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"List users with access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to org\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OrgAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyOrgAccess\",\"tags\":[\"orgs\"],\"summary\":\"Change who has access to org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/OrgAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/orgs/{orgId}/workspaces\":{\"get\":{\"operationId\":\"listWorkspaces\",\"tags\":[\"workspaces\"],\"summary\":\"List workspaces and documents within an org\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"An org's workspaces and documents\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndDomain\"}}}}}}},\"post\":{\"operationId\":\"createWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Create an empty workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/orgIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The workspace id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"integer\",\"description\":\"an identifier for the workspace\",\"example\":155}}}}}}},\"/workspaces/{workspaceId}\":{\"get\":{\"operationId\":\"describeWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Describe a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceWithDocsAndOrg\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Modify a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteWorkspace\",\"tags\":[\"workspaces\"],\"summary\":\"Delete a workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/workspaces/{workspaceId}/docs\":{\"post\":{\"operationId\":\"createDoc\",\"tags\":[\"docs\"],\"summary\":\"Create an empty document\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"settings for the document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The document id\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"string\",\"description\":\"a unique identifier for the document\",\"example\":\"8b97c8db-b4df-4b34-b72c-17459e70140a\"}}}}}}},\"/workspaces/{workspaceId}/access\":{\"get\":{\"operationId\":\"listWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"List users with access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to workspace\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyWorkspaceAccess\",\"tags\":[\"workspaces\"],\"summary\":\"Change who has access to workspace\",\"parameters\":[{\"$ref\":\"#/components/parameters/workspaceIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}\":{\"get\":{\"operationId\":\"describeDoc\",\"tags\":[\"docs\"],\"summary\":\"Describe a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A document's metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocWithWorkspace\"}}}}}},\"patch\":{\"operationId\":\"modifyDoc\",\"tags\":[\"docs\"],\"summary\":\"Modify document metadata (but not its contents)\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocParameters\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"delete\":{\"operationId\":\"deleteDoc\",\"tags\":[\"docs\"],\"summary\":\"Delete a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/move\":{\"patch\":{\"operationId\":\"moveDoc\",\"tags\":[\"docs\"],\"summary\":\"Move document to another workspace.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the target workspace\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"type\":\"integer\",\"example\":597}}}}}},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/access\":{\"get\":{\"operationId\":\"listDocAccess\",\"tags\":[\"docs\"],\"summary\":\"List users with access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Users with access to document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DocAccessRead\"}}}}}},\"patch\":{\"operationId\":\"modifyDocAccess\",\"tags\":[\"docs\"],\"summary\":\"Change who has access to document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"delta\"],\"properties\":{\"delta\":{\"$ref\":\"#/components/schemas/DocAccessWrite\"}}}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/download\":{\"get\":{\"operationId\":\"downloadDoc\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Sqlite file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"nohistory\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove document history (can significantly reduce file size)\"},\"required\":false},{\"in\":\"query\",\"name\":\"template\",\"schema\":{\"type\":\"boolean\",\"description\":\"Remove all data and history but keep the structure to use the document as a template\"},\"required\":false}],\"responses\":{\"200\":{\"description\":\"A document's content in Sqlite form\",\"content\":{\"application/x-sqlite3\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/xlsx\":{\"get\":{\"operationId\":\"downloadDocXlsx\",\"tags\":[\"docs\"],\"summary\":\"Content of document, as an Excel file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A document's content in Excel form\",\"content\":{\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\":{\"schema\":{\"type\":\"string\",\"format\":\"binary\"}}}}}}},\"/docs/{docId}/download/csv\":{\"get\":{\"operationId\":\"downloadDocCsv\",\"tags\":[\"docs\"],\"summary\":\"Content of table, as a CSV file\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's content in CSV form\",\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}}}}}},\"/docs/{docId}/download/table-schema\":{\"get\":{\"operationId\":\"downloadTableSchema\",\"tags\":[\"docs\"],\"summary\":\"The schema of a table\",\"description\":\"The schema follows [frictionlessdata's table-schema standard](https://specs.frictionlessdata.io/table-schema/).\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\",\"description\":\"Name of a table (normalized).\"},\"required\":true},{\"$ref\":\"#/components/parameters/headerQueryParam\"}],\"responses\":{\"200\":{\"description\":\"A table's table-schema in JSON format.\",\"content\":{\"text/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TableSchemaResult\"}}}}}}},\"/docs/{docId}/states/remove\":{\"post\":{\"operationId\":\"deleteActions\",\"tags\":[\"docs\"],\"summary\":\"Truncate the document's action history\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"keep\"],\"properties\":{\"keep\":{\"type\":\"integer\",\"description\":\"The number of the latest history actions to keep\"}},\"example\":{\"keep\":3}}}}}}},\"/docs/{docId}/force-reload\":{\"post\":{\"operationId\":\"forceReload\",\"tags\":[\"docs\"],\"summary\":\"Reload a document\",\"description\":\"Closes and reopens the document, forcing the python engine to restart.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Document reloaded successfully\"}}}},\"/docs/{docId}/tables/{tableId}/data\":{\"get\":{\"operationId\":\"getTableData\",\"tags\":[\"data\"],\"summary\":\"Fetch data from a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"Cells from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}}}}},\"post\":{\"operationId\":\"addRows\",\"tags\":[\"data\"],\"summary\":\"Add rows to a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DataWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}},\"patch\":{\"operationId\":\"modifyRows\",\"tags\":[\"data\"],\"summary\":\"Modify rows of a table\",\"deprecated\":true,\"description\":\"Deprecated in favor of `records` endpoints. We have no immediate plans to remove these endpoints, but consider `records` a better starting point for new projects.\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the data to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Data\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of rows modified\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/tables/{tableId}/data/delete\":{\"post\":{\"operationId\":\"deleteRows\",\"tags\":[\"data\"],\"summary\":\"Delete rows of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the IDs of rows to remove\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Nothing returned\"}}}},\"/docs/{docId}/attachments\":{\"get\":{\"operationId\":\"listAttachments\",\"tags\":[\"attachments\"],\"summary\":\"List metadata of all attachments in a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"}],\"responses\":{\"200\":{\"description\":\"List of attachment metadata records. Note that the list may temporarily include records for attachments that are stored in the document but not referenced by any Attachments type cell.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadataList\"}}}}}},\"post\":{\"operationId\":\"uploadAttachments\",\"tags\":[\"attachments\"],\"summary\":\"Upload attachments to a doc\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the files to add to the doc\",\"content\":{\"multipart/form-data\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentUpload\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of attachments added, one per file.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RowIds\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}\":{\"get\":{\"operationId\":\"getAttachmentMetadata\",\"tags\":[\"attachments\"],\"summary\":\"Get the metadata for an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment metadata\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}}},\"/docs/{docId}/attachments/{attachmentId}/download\":{\"get\":{\"operationId\":\"downloadAttachment\",\"tags\":[\"attachments\"],\"summary\":\"Download the contents of an attachment\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"attachmentId\",\"schema\":{\"$ref\":\"#/components/schemas/AttachmentId\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Attachment contents, with suitable Content-Type.\"}}}},\"/docs/{docId}/tables/{tableId}/records\":{\"get\":{\"operationId\":\"listRecords\",\"tags\":[\"records\"],\"summary\":\"Fetch records from a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/filterQueryParam\"},{\"$ref\":\"#/components/parameters/sortQueryParam\"},{\"$ref\":\"#/components/parameters/limitQueryParam\"},{\"$ref\":\"#/components/parameters/sortHeaderParam\"},{\"$ref\":\"#/components/parameters/limitHeaderParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"Records from the table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}}}}},\"post\":{\"operationId\":\"addRecords\",\"tags\":[\"records\"],\"summary\":\"Add records to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutId\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"IDs of records added\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyRecords\",\"tags\":[\"records\"],\"summary\":\"Modify records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"}],\"requestBody\":{\"description\":\"the records to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceRecords\",\"tags\":[\"records\"],\"summary\":\"Add or update records of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/noparseQueryParam\"},{\"in\":\"query\",\"name\":\"onmany\",\"schema\":{\"type\":\"string\",\"enum\":[\"first\",\"none\",\"all\"],\"description\":\"Which records to update if multiple records are found to match `require`.\\n * `first` - the first matching record (default)\\n * `none` - do not update anything\\n * `all` - update all matches\\n\"}},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding records.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating records.\"}},{\"in\":\"query\",\"name\":\"allow_empty_require\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to allow `require` in the body to be empty, which will match and update all records in the table.\"}}],\"requestBody\":{\"description\":\"The records to add or update. Instead of an id, a `require` object is provided, with the same structure as `fields`. If no query parameter options are set, then the operation is as follows. First, we check if a record exists matching the values specified for columns in `require`. If so, we update it by setting the values specified for columns in `fields`. If not, we create a new record with a combination of the values in `require` and `fields`, with `fields` taking priority if the same column is specified in both. The query parameters allow for variations on this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordsWithRequire\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables\":{\"get\":{\"operationId\":\"listTables\",\"tags\":[\"tables\"],\"summary\":\"List tables in a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"The tables in a document\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}}}}},\"post\":{\"operationId\":\"addTables\",\"tags\":[\"tables\"],\"summary\":\"Add tables to a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateTables\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The table created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyTables\",\"tags\":[\"tables\"],\"summary\":\"Modify tables of a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"the tables to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TablesList\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns\":{\"get\":{\"operationId\":\"listColumns\",\"tags\":[\"columns\"],\"summary\":\"List columns in a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/hiddenQueryParam\"}],\"responses\":{\"200\":{\"description\":\"The columns in a table\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsList\"}}}}}},\"post\":{\"operationId\":\"addColumns\",\"tags\":[\"columns\"],\"summary\":\"Add columns to a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to add\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CreateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"The columns created\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ColumnsWithoutFields\"}}}}}},\"patch\":{\"operationId\":\"modifyColumns\",\"tags\":[\"columns\"],\"summary\":\"Modify columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"}],\"requestBody\":{\"description\":\"the columns to change, with ids\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}},\"put\":{\"operationId\":\"replaceColumns\",\"tags\":[\"columns\"],\"summary\":\"Add or update columns of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"in\":\"query\",\"name\":\"noadd\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit adding columns.\"}},{\"in\":\"query\",\"name\":\"noupdate\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to prohibit updating columns.\"}},{\"in\":\"query\",\"name\":\"replaceall\",\"schema\":{\"type\":\"boolean\",\"description\":\"Set to true to remove existing columns (except the hidden ones) that are not specified in the request body.\"}}],\"requestBody\":{\"description\":\"The columns to add or update. We check whether the specified column ID exists: if so, the column is updated with the provided data, otherwise a new column is created.\\nAlso note that some query parameters alter this behavior.\\n\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/UpdateColumns\"}}},\"required\":true},\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/tables/{tableId}/columns/{colId}\":{\"delete\":{\"operationId\":\"deleteColumn\",\"tags\":[\"columns\"],\"summary\":\"Delete a column of a table\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"$ref\":\"#/components/parameters/tableIdPathParam\"},{\"$ref\":\"#/components/parameters/colIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success\"}}}},\"/docs/{docId}/webhooks\":{\"get\":{\"tags\":[\"webhooks\"],\"summary\":\"Webhooks associated with a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"A list of webhooks.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/Webhooks\"}}}}}}}},\"post\":{\"tags\":[\"webhooks\"],\"summary\":\"Create new webhooks for a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"an array of webhook settings\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}}}}}}},\"responses\":{\"200\":{\"description\":\"Success\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"webhooks\"],\"properties\":{\"webhooks\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/WebhookId\"}}}}}}}}}},\"/docs/{docId}/webhooks/{webhookId}\":{\"patch\":{\"tags\":[\"webhooks\"],\"summary\":\"Modify a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"requestBody\":{\"description\":\"the changes to make\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WebhookPartialFields\"}}}},\"responses\":{\"200\":{\"description\":\"Success.\"}}},\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Remove a webhook\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"path\",\"name\":\"webhookId\",\"schema\":{\"type\":\"string\"},\"required\":true}],\"responses\":{\"200\":{\"description\":\"Success.\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"success\"],\"properties\":{\"success\":{\"type\":\"boolean\",\"example\":true}}}}}}}}},\"/docs/{docId}/webhooks/queue\":{\"delete\":{\"tags\":[\"webhooks\"],\"summary\":\"Empty a document's queue of undelivered payloads\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"responses\":{\"200\":{\"description\":\"Success.\"}}}},\"/docs/{docId}/sql\":{\"get\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"},{\"in\":\"query\",\"name\":\"q\",\"schema\":{\"type\":\"string\",\"description\":\"The SQL query to run. This GET endpoint is a simplified version of the corresponding POST endpoint, without support for parameters or options. See the POST endpoint for details of what's allowed in the SQL query string.\"}}],\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}},\"post\":{\"tags\":[\"sql\"],\"summary\":\"Run an SQL query against a document, with options or parameters\",\"parameters\":[{\"$ref\":\"#/components/parameters/docIdPathParam\"}],\"requestBody\":{\"description\":\"Query options\",\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"sql\"],\"properties\":{\"sql\":{\"type\":\"string\",\"description\":\"The SQL query to run. Must be a single SELECT statement, with no trailing semicolon. WITH clauses are permitted. All Grist documents are currently SQLite databases, and the SQL query is interpreted and run by SQLite, with various defensive measures. Statements that would modify the database are not supported.\",\"example\":\"select * from Pets where popularity >= ?\"},\"args\":{\"type\":\"array\",\"items\":{\"oneOf\":[{\"type\":\"number\"},{\"type\":\"string\"}]},\"description\":\"Parameters for the query.\",\"example\":[50]},\"timeout\":{\"type\":\"number\",\"description\":\"Timeout after which operations on the document will be interrupted. Specified in milliseconds. Defaults to 1000 (1 second). This default is controlled by an optional environment variable read by the Grist app, GRIST_SQL_TIMEOUT_MSEC. The default cannot be exceeded, only reduced.\",\"example\":500}}}}}},\"responses\":{\"200\":{\"description\":\"The result set for the query.\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SqlResultSet\"}}}}}}},\"/users/{userId}\":{\"delete\":{\"tags\":[\"users\"],\"summary\":\"Delete a user from Grist\",\"description\":\"This action also deletes the user's personal organisation and all the workspaces and documents it contains.\\nCurrently, only the users themselves are allowed to delete their own accounts.\\n\\n\u26a0\ufe0f **This action cannot be undone, please be cautious when using this endpoint** \u26a0\ufe0f\\n\",\"parameters\":[{\"$ref\":\"#/components/parameters/userIdPathParam\"}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The user's name to delete (for confirmation, to avoid deleting the wrong account).\",\"example\":\"John Doe\"}}}}}},\"responses\":{\"200\":{\"description\":\"The account has been deleted successfully\"},\"400\":{\"description\":\"The passed user name does not match the one retrieved from the database given the passed user id\"},\"403\":{\"description\":\"The caller is not allowed to delete this account\"},\"404\":{\"description\":\"The user is not found\"}}}}},\"tags\":[{\"name\":\"orgs\",\"description\":\"Team sites and personal spaces are called 'orgs' in the API.\"},{\"name\":\"workspaces\",\"description\":\"Sites can be organized into groups of documents called workspaces.\"},{\"name\":\"docs\",\"description\":\"Workspaces contain collections of Grist documents.\"},{\"name\":\"records\",\"description\":\"Tables contain collections of records (also called rows).\"},{\"name\":\"tables\",\"description\":\"Documents are structured as a collection of tables.\"},{\"name\":\"columns\",\"description\":\"Tables are structured as a collection of columns.\"},{\"name\":\"data\",\"description\":\"Work with table data, using a (now deprecated) columnar format. We now recommend the `records` endpoints.\"},{\"name\":\"attachments\",\"description\":\"Documents may include attached files. Data records can refer to these using a column of type `Attachments`.\"},{\"name\":\"webhooks\",\"description\":\"Document changes can trigger requests to URLs called webhooks.\"},{\"name\":\"sql\",\"description\":\"Sql endpoint to query data from documents.\"},{\"name\":\"users\",\"description\":\"Grist users.\"}],\"components\":{\"securitySchemes\":{\"ApiKey\":{\"type\":\"http\",\"scheme\":\"bearer\",\"bearerFormat\":\"Authorization: Bearer XXXXXXXXXXX\",\"description\":\"Access to the Grist API is controlled by an Authorization header, which should contain the word 'Bearer', followed by a space, followed by your API key.\"}},\"schemas\":{\"Org\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"domain\",\"owner\",\"createdAt\",\"updatedAt\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":42},\"name\":{\"type\":\"string\",\"example\":\"Grist Labs\"},\"domain\":{\"type\":\"string\",\"nullable\":true,\"example\":\"gristlabs\"},\"owner\":{\"type\":\"object\",\"$ref\":\"#/components/schemas/User\",\"nullable\":true},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"createdAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"},\"updatedAt\":{\"type\":\"string\",\"example\":\"2019-09-13T15:42:35.000Z\"}}},\"Orgs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Org\"}},\"Webhooks\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Webhook\"}},\"Webhook\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"format\":\"uuid\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"},\"fields\":{\"$ref\":\"#/components/schemas/WebhookFields\"},\"usage\":{\"$ref\":\"#/components/schemas/WebhookUsage\"}}},\"WebhookFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WebhookPartialFields\"},{\"$ref\":\"#/components/schemas/WebhookRequiredFields\"}]},\"WebhookRequiredFields\":{\"type\":\"object\",\"required\":[\"name\",\"memo\",\"url\",\"enabled\",\"unsubscribeKey\",\"eventTypes\",\"isReadyColumn\",\"tableId\"],\"properties\":{\"unsubscribeKey\":{\"type\":\"string\"}}},\"WebhookPartialFields\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"new-project-email\",\"nullable\":true},\"memo\":{\"type\":\"string\",\"example\":\"Send an email when a project is added\",\"nullable\":true},\"url\":{\"type\":\"string\",\"format\":\"uri\",\"example\":\"https://example.com/webhook/123\"},\"enabled\":{\"type\":\"boolean\"},\"eventTypes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"example\":[\"add\",\"update\"]},\"isReadyColumn\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"tableId\":{\"type\":\"string\",\"example\":\"Projects\"}}},\"WebhookUsage\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"numWaiting\",\"status\"],\"properties\":{\"numWaiting\":{\"type\":\"integer\"},\"status\":{\"type\":\"string\",\"example\":\"idle\"},\"updatedTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastSuccessTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastFailureTime\":{\"type\":\"number\",\"nullable\":true,\"format\":\"UNIX epoch in milliseconds\",\"example\":1685637500424},\"lastErrorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"lastHttpStatus\":{\"type\":\"number\",\"nullable\":true,\"example\":200},\"lastEventBatch\":{\"$ref\":\"#/components/schemas/WebhookBatchStatus\"}}},\"WebhookBatchStatus\":{\"type\":\"object\",\"nullable\":true,\"required\":[\"size\",\"attempts\",\"status\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}}},\"WebhookId\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Webhook identifier\",\"example\":\"xxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx\"}}},\"WebhookRequiredProperties\":{\"type\":\"object\",\"required\":[\"size\"],\"properties\":{\"size\":{\"type\":\"number\",\"example\":1}}},\"WebhookProperties\":{\"size\":{\"type\":\"number\",\"example\":1},\"attempts\":{\"type\":\"number\",\"example\":1},\"errorMessage\":{\"type\":\"string\",\"nullable\":true,\"example\":null},\"httpStatus\":{\"type\":\"number\",\"example\":200},\"status\":{\"type\":\"string\",\"example\":\"success\"}},\"Workspace\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"access\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":97},\"name\":{\"type\":\"string\",\"example\":\"Secret Plans\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"}}},\"Doc\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"isPinned\",\"urlId\",\"access\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":145},\"name\":{\"type\":\"string\",\"example\":\"Project Lollipop\"},\"access\":{\"type\":\"string\",\"$ref\":\"#/components/schemas/Access\"},\"isPinned\":{\"type\":\"boolean\",\"example\":true},\"urlId\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"WorkspaceWithDocs\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"docs\"],\"properties\":{\"docs\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/components/schemas/Doc\"}}}}]},\"WorkspaceWithDocsAndDomain\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"type\":\"object\",\"properties\":{\"orgDomain\":{\"type\":\"string\",\"example\":\"gristlabs\"}}}]},\"WorkspaceWithOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Workspace\"},{\"type\":\"object\",\"required\":[\"org\"],\"properties\":{\"org\":{\"$ref\":\"#/components/schemas/Org\"}}}]},\"WorkspaceWithDocsAndOrg\":{\"allOf\":[{\"$ref\":\"#/components/schemas/WorkspaceWithDocs\"},{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}]},\"DocWithWorkspace\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Doc\"},{\"type\":\"object\",\"required\":[\"workspace\"],\"properties\":{\"workspace\":{\"$ref\":\"#/components/schemas/WorkspaceWithOrg\"}}}]},\"User\":{\"type\":\"object\",\"required\":[\"id\",\"name\",\"picture\"],\"properties\":{\"id\":{\"type\":\"integer\",\"format\":\"int64\",\"example\":101},\"name\":{\"type\":\"string\",\"example\":\"Helga Hufflepuff\"},\"picture\":{\"type\":\"string\",\"nullable\":true,\"example\":null}}},\"Access\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\"]},\"Data\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"}}},\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"id\":[1,2],\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"DataWithoutId\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"array\",\"items\":{\"type\":\"object\"}},\"example\":{\"pet\":[\"cat\",\"dog\"],\"popularity\":[67,95]}},\"RecordsList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"id\":1,\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"id\":2,\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutId\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\",\"description\":\"A JSON object mapping column names to [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues).\"}}}}},\"example\":{\"records\":[{\"fields\":{\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"pet\":\"dog\",\"popularity\":95}}]}},\"RecordsWithoutFields\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1}}}}},\"example\":{\"records\":[{\"id\":1},{\"id\":2}]}},\"RecordsWithRequire\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"require\"],\"properties\":{\"require\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) we want to have in those columns (either by matching with an existing record, or creating a new record)\\n\"},\"fields\":{\"type\":\"object\",\"description\":\"keys are column identifiers, and values are [cell values](https://support.getgrist.com/code/interfaces/grist_plugin_api.rowrecord/#cellvalues) to place in those columns (either overwriting values in an existing record, or in a new record)\\n\"}}}}},\"example\":{\"records\":[{\"require\":{\"pet\":\"cat\"},\"fields\":{\"popularity\":67}},{\"require\":{\"pet\":\"dog\"},\"fields\":{\"popularity\":95}}]}},\"TablesList\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"fields\":{\"type\":\"object\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"fields\":{\"tableRef\":1,\"onDemand\":true}},{\"id\":\"Places\",\"fields\":{\"tableRef\":2,\"onDemand\":false}}]}},\"TablesWithoutFields\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"}}}}},\"example\":{\"tables\":[{\"id\":\"People\"},{\"id\":\"Places\"}]}},\"CreateTables\":{\"type\":\"object\",\"required\":[\"tables\"],\"properties\":{\"tables\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"Table1\"},\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"type\":\"object\"}}}}}}}},\"example\":{\"tables\":[{\"id\":\"People\",\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\"}}]}]}},\"ColumnsList\":{\"type\":\"object\",\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"},\"fields\":{\"$ref\":\"#/components/schemas/GetFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"CreateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"$ref\":\"#/components/schemas/CreateFields\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}},{\"id\":\"Order\",\"fields\":{\"type\":\"Ref:Orders\",\"visibleCol\":2}},{\"id\":\"Formula\",\"fields\":{\"type\":\"Int\",\"formula\":\"$A + $B\",\"isFormula\":true}},{\"id\":\"Status\",\"fields\":{\"type\":\"Choice\",\"widgetOptions\":\"{\\\"choices\\\":[\\\"New\\\",\\\"Old\\\"],\\\"choiceOptions\\\":{\\\"New\\\":{\\\"fillColor\\\":\\\"#FF0000\\\",\\\"textColor\\\":\\\"#FFFFFF\\\"}}}\"}}]}},\"UpdateColumns\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Column identifier\"},\"fields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/CreateFields\"},{\"type\":\"object\",\"properties\":{\"colId\":{\"type\":\"string\",\"description\":\"Set it to the new column ID when you want to change it.\"}}}]}}}}},\"example\":{\"columns\":[{\"id\":\"pet\",\"fields\":{\"label\":\"Pet\"}},{\"id\":\"popularity\",\"fields\":{\"label\":\"Popularity \u2764\",\"type\":\"Int\"}}]}},\"ColumnsWithoutFields\":{\"type\":\"object\",\"required\":[\"columns\"],\"properties\":{\"columns\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\"],\"properties\":{\"id\":{\"type\":\"string\",\"example\":\"ColumnName\"}}}}},\"example\":{\"columns\":[{\"id\":\"pet\"},{\"id\":\"popularity\"}]}},\"Fields\":{\"type\":\"object\",\"properties\":{\"type\":{\"type\":\"string\",\"description\":\"Column type, by default Any. Ref, RefList and DateTime types requires a postfix, e.g. DateTime:America/New_York, Ref:Users\",\"enum\":[\"Any\",\"Text\",\"Numeric\",\"Int\",\"Bool\",\"Date\",\"DateTime:\",\"Choice\",\"ChoiceList\",\"Ref:\",\"RefList:\",\"Attachments\"]},\"label\":{\"type\":\"string\",\"description\":\"Column label.\"},\"formula\":{\"type\":\"string\",\"description\":\"A python formula, e.g.: $A + Table1.lookupOne(B=$B)\"},\"isFormula\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column is a formula column. Use \\\"false\\\" for trigger formula column.\"},\"widgetOptions\":{\"type\":\"string\",\"description\":\"A JSON object with widget options, e.g.: {\\\"choices\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"alignment\\\": \\\"right\\\"}\"},\"untieColIdFromLabel\":{\"type\":\"boolean\",\"description\":\"Use \\\"true\\\" to indicate that the column label should not be used as the column identifier. Use \\\"false\\\" to use the label as the identifier.\"},\"recalcWhen\":{\"type\":\"integer\",\"description\":\"A number indicating when the column should be recalculated.
    1. On new records or when any field in recalcDeps changes, it's a 'data-cleaning'.
    2. Never.
    3. Calculate on new records and on manual updates to any data field.
    \"},\"visibleCol\":{\"type\":\"integer\",\"description\":\"For Ref and RefList columns, the colRef of a column to display\"}}},\"CreateFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"string\",\"description\":\"An encoded array of column identifiers (colRefs) that this column depends on. If any of these columns change, the column will be recalculated. E.g.: [2, 3]\"}}}]},\"GetFields\":{\"allOf\":[{\"$ref\":\"#/components/schemas/Fields\"},{\"type\":\"object\",\"properties\":{\"recalcDeps\":{\"type\":\"array\",\"items\":{\"type\":\"integer\"},\"description\":\"An array of column identifiers (colRefs) that this column depends on, prefixed with \\\"L\\\" constant. If any of these columns change, the column will be recalculated. E.g.: [\\\"L\\\", 2, 3]\"},\"colRef\":{\"type\":\"integer\",\"description\":\"Column reference, e.g.: 2\"}}}]},\"RowIds\":{\"type\":\"array\",\"example\":[101,102,103],\"items\":{\"type\":\"integer\"}},\"DocParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Competitive Analysis\"},\"isPinned\":{\"type\":\"boolean\",\"example\":false}}},\"WorkspaceParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"Retreat Docs\"}}},\"OrgParameters\":{\"type\":\"object\",\"properties\":{\"name\":{\"type\":\"string\",\"example\":\"ACME Unlimited\"}}},\"OrgAccessRead\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"OrgAccessWrite\":{\"type\":\"object\",\"required\":[\"users\"],\"properties\":{\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"WorkspaceAccessRead\":{\"type\":\"object\",\"required\":[\"maxInheritedRole\",\"users\"],\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"name\"],\"properties\":{\"id\":{\"type\":\"integer\",\"example\":1},\"name\":{\"type\":\"string\",\"example\":\"Andrea\"},\"email\":{\"type\":\"string\",\"example\":\"andrea@getgrist.com\"},\"access\":{\"$ref\":\"#/components/schemas/Access\"},\"parentAccess\":{\"$ref\":\"#/components/schemas/Access\"}}}}}},\"WorkspaceAccessWrite\":{\"type\":\"object\",\"properties\":{\"maxInheritedRole\":{\"$ref\":\"#/components/schemas/Access\"},\"users\":{\"type\":\"object\",\"additionalProperties\":{\"type\":\"string\",\"enum\":[\"owners\",\"editors\",\"viewers\",\"members\",null]},\"example\":{\"foo@getgrist.com\":\"owners\",\"bar@getgrist.com\":null}}}},\"DocAccessWrite\":{\"$ref\":\"#/components/schemas/WorkspaceAccessWrite\"},\"DocAccessRead\":{\"$ref\":\"#/components/schemas/WorkspaceAccessRead\"},\"AttachmentUpload\":{\"type\":\"object\",\"properties\":{\"upload\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"format\":\"binary\"}}}},\"AttachmentId\":{\"type\":\"number\",\"description\":\"An integer ID\"},\"AttachmentMetadata\":{\"type\":\"object\",\"properties\":{\"fileName\":{\"type\":\"string\",\"example\":\"logo.png\"},\"fileSize\":{\"type\":\"number\",\"example\":12345},\"timeUploaded\":{\"type\":\"string\",\"example\":\"2020-02-13T12:17:19.000Z\"}}},\"AttachmentMetadataList\":{\"type\":\"object\",\"required\":[\"records\"],\"properties\":{\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"id\",\"fields\"],\"properties\":{\"id\":{\"type\":\"number\",\"example\":1},\"fields\":{\"$ref\":\"#/components/schemas/AttachmentMetadata\"}}}}}},\"SqlResultSet\":{\"type\":\"object\",\"required\":[\"statement\",\"records\"],\"properties\":{\"statement\":{\"type\":\"string\",\"description\":\"A copy of the SQL statement.\",\"example\":\"select * from Pets ...\"},\"records\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"object\"}}},\"example\":[{\"fields\":{\"id\":1,\"pet\":\"cat\",\"popularity\":67}},{\"fields\":{\"id\":2,\"pet\":\"dog\",\"popularity\":95}}]}}},\"TableSchemaResult\":{\"type\":\"object\",\"required\":[\"name\",\"title\",\"schema\"],\"properties\":{\"name\":{\"type\":\"string\",\"description\":\"The ID (technical name) of the table\"},\"title\":{\"type\":\"string\",\"description\":\"The human readable name of the table\"},\"path\":{\"type\":\"string\",\"description\":\"The URL to download the CSV\",\"example\":\"https://getgrist.com/o/docs/api/docs/ID/download/csv?tableId=Table1&....\"},\"format\":{\"type\":\"string\",\"enum\":[\"csv\"]},\"mediatype\":{\"type\":\"string\",\"enum\":[\"text/csv\"]},\"encoding\":{\"type\":\"string\",\"enum\":[\"utf-8\"]},\"dialect\":{\"$ref\":\"#/components/schemas/csv-dialect\"},\"schema\":{\"$ref\":\"#/components/schemas/table-schema\"}}},\"csv-dialect\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"CSV Dialect\",\"description\":\"The CSV dialect descriptor.\",\"type\":[\"string\",\"object\"],\"required\":[\"delimiter\",\"doubleQuote\"],\"properties\":{\"csvddfVersion\":{\"title\":\"CSV Dialect schema version\",\"description\":\"A number to indicate the schema version of CSV Dialect. Version 1.0 was named CSV Dialect Description Format and used different field names.\",\"type\":\"number\",\"default\":1.2,\"examples:\":[\"{\\n \\\"csvddfVersion\\\": \\\"1.2\\\"\\n}\\n\"]},\"delimiter\":{\"title\":\"Delimiter\",\"description\":\"A character sequence to use as the field separator.\",\"type\":\"string\",\"default\":\",\",\"examples\":[\"{\\n \\\"delimiter\\\": \\\",\\\"\\n}\\n\",\"{\\n \\\"delimiter\\\": \\\";\\\"\\n}\\n\"]},\"doubleQuote\":{\"title\":\"Double Quote\",\"description\":\"Specifies the handling of quotes inside fields.\",\"context\":\"If Double Quote is set to true, two consecutive quotes must be interpreted as one.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"doubleQuote\\\": true\\n}\\n\"]},\"lineTerminator\":{\"title\":\"Line Terminator\",\"description\":\"Specifies the character sequence that must be used to terminate rows.\",\"type\":\"string\",\"default\":\"\\r\\n\",\"examples\":[\"{\\n \\\"lineTerminator\\\": \\\"\\\\r\\\\n\\\"\\n}\\n\",\"{\\n \\\"lineTerminator\\\": \\\"\\\\n\\\"\\n}\\n\"]},\"nullSequence\":{\"title\":\"Null Sequence\",\"description\":\"Specifies the null sequence, for example, `\\\\N`.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"nullSequence\\\": \\\"\\\\N\\\"\\n}\\n\"]},\"quoteChar\":{\"title\":\"Quote Character\",\"description\":\"Specifies a one-character string to use as the quoting character.\",\"type\":\"string\",\"default\":\"\\\"\",\"examples\":[\"{\\n \\\"quoteChar\\\": \\\"'\\\"\\n}\\n\"]},\"escapeChar\":{\"title\":\"Escape Character\",\"description\":\"Specifies a one-character string to use as the escape character.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"escapeChar\\\": \\\"\\\\\\\\\\\"\\n}\\n\"]},\"skipInitialSpace\":{\"title\":\"Skip Initial Space\",\"description\":\"Specifies the interpretation of whitespace immediately following a delimiter. If false, whitespace immediately after a delimiter should be treated as part of the subsequent field.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"skipInitialSpace\\\": true\\n}\\n\"]},\"header\":{\"title\":\"Header\",\"description\":\"Specifies if the file includes a header row, always as the first row in the file.\",\"type\":\"boolean\",\"default\":true,\"examples\":[\"{\\n \\\"header\\\": true\\n}\\n\"]},\"commentChar\":{\"title\":\"Comment Character\",\"description\":\"Specifies that any row beginning with this one-character string, without preceeding whitespace, causes the entire line to be ignored.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"commentChar\\\": \\\"#\\\"\\n}\\n\"]},\"caseSensitiveHeader\":{\"title\":\"Case Sensitive Header\",\"description\":\"Specifies if the case of headers is meaningful.\",\"context\":\"Use of case in source CSV files is not always an intentional decision. For example, should \\\"CAT\\\" and \\\"Cat\\\" be considered to have the same meaning.\",\"type\":\"boolean\",\"default\":false,\"examples\":[\"{\\n \\\"caseSensitiveHeader\\\": true\\n}\\n\"]}},\"examples\":[\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\";\\\"\\n }\\n}\\n\",\"{\\n \\\"dialect\\\": {\\n \\\"delimiter\\\": \\\"\\\\t\\\",\\n \\\"quoteChar\\\": \\\"'\\\",\\n \\\"commentChar\\\": \\\"#\\\"\\n }\\n}\\n\"]},\"table-schema\":{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Table Schema\",\"description\":\"A Table Schema for this resource, compliant with the [Table Schema](/tableschema/) specification.\",\"type\":[\"string\",\"object\"],\"required\":[\"fields\"],\"properties\":{\"fields\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Field\",\"type\":\"object\",\"oneOf\":[{\"type\":\"object\",\"title\":\"String Field\",\"description\":\"The field contains strings, that is, sequences of characters.\",\"required\":[\"name\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `string`.\",\"enum\":[\"string\"]},\"format\":{\"description\":\"The format keyword options for `string` are `default`, `email`, `uri`, `binary`, and `uuid`.\",\"context\":\"The following `format` options are supported:\\n * **default**: any valid string.\\n * **email**: A valid email address.\\n * **uri**: A valid URI.\\n * **binary**: A base64 encoded string representing binary data.\\n * **uuid**: A string that is a uuid.\",\"enum\":[\"default\",\"email\",\"uri\",\"binary\",\"uuid\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `string` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"pattern\":{\"type\":\"string\",\"description\":\"A regular expression pattern to test each value of the property against, where a truthy response indicates validity.\",\"context\":\"Regular expressions `SHOULD` conform to the [XML Schema regular expression syntax](http://www.w3.org/TR/xmlschema-2/#regexs).\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"name\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"constraints\\\": {\\n \\\"minLength\\\": 3,\\n \\\"maxLength\\\": 35\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Number Field\",\"description\":\"The field contains numbers of any kind including decimals.\",\"context\":\"The lexical formatting follows that of decimal in [XMLSchema](https://www.w3.org/TR/xmlschema-2/#decimal): a non-empty finite-length sequence of decimal digits separated by a period as a decimal indicator. An optional leading sign is allowed. If the sign is omitted, '+' is assumed. Leading and trailing zeroes are optional. If the fractional part is zero, the period and following zero(es) can be omitted. For example: '-1.23', '12678967.543233', '+100000.00', '210'.\\n\\nThe following special string values are permitted (case does not need to be respected):\\n - NaN: not a number\\n - INF: positive infinity\\n - -INF: negative infinity\\n\\nA number `MAY` also have a trailing:\\n - exponent: this `MUST` consist of an E followed by an optional + or - sign followed by one or more decimal digits (0-9)\\n - percentage: the percentage sign: `%`. In conversion percentages should be divided by 100.\\n\\nIf both exponent and percentages are present the percentage `MUST` follow the exponent e.g. '53E10%' (equals 5.3).\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `number`.\",\"enum\":[\"number\"]},\"format\":{\"description\":\"There are no format keyword options for `number`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"decimalChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to represent a decimal point within the number. The default value is `.`.\"},\"groupChar\":{\"type\":\"string\",\"description\":\"A string whose value is used to group digits within the number. The default value is `null`. A common value is `,` e.g. '100,000'.\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `number` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"number\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"number\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"field-name\\\",\\n \\\"type\\\": \\\"number\\\",\\n \\\"constraints\\\": {\\n \\\"enum\\\": [ \\\"1.00\\\", \\\"1.50\\\", \\\"2.00\\\" ]\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Integer Field\",\"description\":\"The field contains integers - that is whole numbers.\",\"context\":\"Integer values are indicated in the standard way for any valid integer.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `integer`.\",\"enum\":[\"integer\"]},\"format\":{\"description\":\"There are no format keyword options for `integer`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"bareNumber\":{\"type\":\"boolean\",\"title\":\"bareNumber\",\"description\":\"a boolean field with a default of `true`. If `true` the physical contents of this field must follow the formatting constraints already set out. If `false` the contents of this field may contain leading and/or trailing non-numeric characters (which implementors MUST therefore strip). The purpose of `bareNumber` is to allow publishers to publish numeric data that contains trailing characters such as percentages e.g. `95%` or leading characters such as currencies e.g. `\u20ac95` or `EUR 95`. Note that it is entirely up to implementors what, if anything, they do with stripped text.\",\"default\":true},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `integer` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\",\\n \\\"constraints\\\": {\\n \\\"unique\\\": true,\\n \\\"minimum\\\": 100,\\n \\\"maximum\\\": 9999\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Field\",\"description\":\"The field contains temporal date values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `date`.\",\"enum\":[\"date\"]},\"format\":{\"description\":\"The format keyword options for `date` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string of YYYY-MM-DD.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `date` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": \\\"01-01-1900\\\"\\n }\\n}\\n\",\"{\\n \\\"name\\\": \\\"date_of_birth\\\",\\n \\\"type\\\": \\\"date\\\",\\n \\\"format\\\": \\\"MM-DD-YYYY\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Time Field\",\"description\":\"The field contains temporal time values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `time`.\",\"enum\":[\"time\"]},\"format\":{\"description\":\"The format keyword options for `time` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for time.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `time` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"appointment_start\\\",\\n \\\"type\\\": \\\"time\\\",\\n \\\"format\\\": \\\"any\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Date Time Field\",\"description\":\"The field contains temporal datetime values.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `datetime`.\",\"enum\":[\"datetime\"]},\"format\":{\"description\":\"The format keyword options for `datetime` are `default`, `any`, and `{PATTERN}`.\",\"context\":\"The following `format` options are supported:\\n * **default**: An ISO8601 format string for datetime.\\n * **any**: Any parsable representation of a date. The implementing library can attempt to parse the datetime via a range of strategies.\\n * **{PATTERN}**: The value can be parsed according to `{PATTERN}`, which `MUST` follow the date formatting syntax of C / Python [strftime](http://strftime.org/).\",\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `datetime` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"timestamp\\\",\\n \\\"type\\\": \\\"datetime\\\",\\n \\\"format\\\": \\\"default\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Field\",\"description\":\"A calendar year, being an integer with 4 digits. Equivalent to [gYear in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYear)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `year`.\",\"enum\":[\"year\"]},\"format\":{\"description\":\"There are no format keyword options for `year`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `year` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"integer\"}}]},\"minimum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]},\"maximum\":{\"oneOf\":[{\"type\":\"string\"},{\"type\":\"integer\"}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"year\\\",\\n \\\"type\\\": \\\"year\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1970,\\n \\\"maximum\\\": 2003\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Year Month Field\",\"description\":\"A calendar year month, being an integer with 1 or 2 digits. Equivalent to [gYearMonth in XML Schema](https://www.w3.org/TR/xmlschema-2/#gYearMonth)\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `yearmonth`.\",\"enum\":[\"yearmonth\"]},\"format\":{\"description\":\"There are no format keyword options for `yearmonth`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `yearmonth` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"month\\\",\\n \\\"type\\\": \\\"yearmonth\\\",\\n \\\"constraints\\\": {\\n \\\"minimum\\\": 1,\\n \\\"maximum\\\": 6\\n }\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Boolean Field\",\"description\":\"The field contains boolean (true/false) data.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `boolean`.\",\"enum\":[\"boolean\"]},\"format\":{\"description\":\"There are no format keyword options for `boolean`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"trueValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"true\",\"True\",\"TRUE\",\"1\"]},\"falseValues\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"type\":\"string\"},\"default\":[\"false\",\"False\",\"FALSE\",\"0\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `boolean` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"boolean\"}}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"registered\\\",\\n \\\"type\\\": \\\"boolean\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Object Field\",\"description\":\"The field contains data which can be parsed as a valid JSON object.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `object`.\",\"enum\":[\"object\"]},\"format\":{\"description\":\"There are no format keyword options for `object`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `object` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"extra\\\"\\n \\\"type\\\": \\\"object\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoPoint Field\",\"description\":\"The field contains data describing a geographic point.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geopoint`.\",\"enum\":[\"geopoint\"]},\"format\":{\"description\":\"The format keyword options for `geopoint` are `default`,`array`, and `object`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A string of the pattern 'lon, lat', where `lon` is the longitude and `lat` is the latitude.\\n * **array**: An array of exactly two items, where each item is either a number, or a string parsable as a number, and the first item is `lon` and the second item is `lat`.\\n * **object**: A JSON object with exactly two keys, `lat` and `lon`\",\"notes\":[\"Implementations `MUST` strip all white space in the default format of `lon, lat`.\"],\"enum\":[\"default\",\"array\",\"object\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geopoint` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"post_office\\\",\\n \\\"type\\\": \\\"geopoint\\\",\\n \\\"format\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"GeoJSON Field\",\"description\":\"The field contains a JSON object according to GeoJSON or TopoJSON\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `geojson`.\",\"enum\":[\"geojson\"]},\"format\":{\"description\":\"The format keyword options for `geojson` are `default` and `topojson`.\",\"context\":\"The following `format` options are supported:\\n * **default**: A geojson object as per the [GeoJSON spec](http://geojson.org/).\\n * **topojson**: A topojson object as per the [TopoJSON spec](https://github.com/topojson/topojson-specification/blob/master/README.md)\",\"enum\":[\"default\",\"topojson\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `geojson` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"object\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\"\\n}\\n\",\"{\\n \\\"name\\\": \\\"city_limits\\\",\\n \\\"type\\\": \\\"geojson\\\",\\n \\\"format\\\": \\\"topojson\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Array Field\",\"description\":\"The field contains data which can be parsed as a valid JSON array.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `array`.\",\"enum\":[\"array\"]},\"format\":{\"description\":\"There are no format keyword options for `array`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply for `array` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"array\"}}]},\"minLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the minimum length of a value.\"},\"maxLength\":{\"type\":\"integer\",\"description\":\"An integer that specifies the maximum length of a value.\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"options\\\"\\n \\\"type\\\": \\\"array\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Duration Field\",\"description\":\"The field contains a duration of time.\",\"context\":\"The lexical representation for duration is the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) extended format `PnYnMnDTnHnMnS`, where `nY` represents the number of years, `nM` the number of months, `nD` the number of days, 'T' is the date/time separator, `nH` the number of hours, `nM` the number of minutes and `nS` the number of seconds. The number of seconds can include decimal digits to arbitrary precision. Date and time elements including their designator may be omitted if their value is zero, and lower order elements may also be omitted for reduced precision. Here we follow the definition of [XML Schema duration datatype](http://www.w3.org/TR/xmlschema-2/#duration) directly and that definition is implicitly inlined here.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `duration`.\",\"enum\":[\"duration\"]},\"format\":{\"description\":\"There are no format keyword options for `duration`: only `default` is allowed.\",\"enum\":[\"default\"],\"default\":\"default\"},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints are supported for `duration` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},\"minimum\":{\"type\":\"string\"},\"maximum\":{\"type\":\"string\"}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"period\\\"\\n \\\"type\\\": \\\"duration\\\"\\n}\\n\"]},{\"type\":\"object\",\"title\":\"Any Field\",\"description\":\"Any value is accepted, including values that are not captured by the type/format/constraint requirements of the specification.\",\"required\":[\"name\",\"type\"],\"properties\":{\"name\":{\"title\":\"Name\",\"description\":\"A name for this field.\",\"type\":\"string\"},\"title\":{\"title\":\"Title\",\"description\":\"A human-readable title.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"title\\\": \\\"My Package Title\\\"\\n}\\n\"]},\"description\":{\"title\":\"Description\",\"description\":\"A text description. Markdown is encouraged.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"description\\\": \\\"# My Package description\\\\nAll about my package.\\\"\\n}\\n\"]},\"example\":{\"title\":\"Example\",\"description\":\"An example value for the field.\",\"type\":\"string\",\"examples\":[\"{\\n \\\"example\\\": \\\"Put here an example value for your field\\\"\\n}\\n\"]},\"type\":{\"description\":\"The type keyword, which `MUST` be a value of `any`.\",\"enum\":[\"any\"]},\"constraints\":{\"title\":\"Constraints\",\"description\":\"The following constraints apply to `any` fields.\",\"type\":\"object\",\"properties\":{\"required\":{\"type\":\"boolean\",\"description\":\"Indicates whether a property must have a value for each instance.\",\"context\":\"An empty string is considered to be a missing value.\"},\"unique\":{\"type\":\"boolean\",\"description\":\"When `true`, each value for the property `MUST` be unique.\"},\"enum\":{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true}}},\"rdfType\":{\"type\":\"string\",\"description\":\"The RDF type for this field.\"}},\"examples\":[\"{\\n \\\"name\\\": \\\"notes\\\",\\n \\\"type\\\": \\\"any\\\"\\n\"]}]},\"description\":\"An `array` of Table Schema Field objects.\",\"examples\":[\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\"\\n }\\n ]\\n}\\n\",\"{\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"my-field-name\\\",\\n \\\"type\\\": \\\"number\\\"\\n },\\n {\\n \\\"name\\\": \\\"my-field-name-2\\\",\\n \\\"type\\\": \\\"string\\\",\\n \\\"format\\\": \\\"email\\\"\\n }\\n ]\\n}\\n\"]},\"primaryKey\":{\"oneOf\":[{\"type\":\"array\",\"minItems\":1,\"uniqueItems\":true,\"items\":{\"type\":\"string\"}},{\"type\":\"string\"}],\"description\":\"A primary key is a field name or an array of field names, whose values `MUST` uniquely identify each row in the table.\",\"context\":\"Field name in the `primaryKey` `MUST` be unique, and `MUST` match a field name in the associated table. It is acceptable to have an array with a single value, indicating that the value of a single field is the primary key.\",\"examples\":[\"{\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n}\\n\",\"{\\n \\\"primaryKey\\\": [\\n \\\"first_name\\\",\\n \\\"last_name\\\"\\n ]\\n}\\n\"]},\"foreignKeys\":{\"type\":\"array\",\"minItems\":1,\"items\":{\"title\":\"Table Schema Foreign Key\",\"description\":\"Table Schema Foreign Key\",\"type\":\"object\",\"required\":[\"fields\",\"reference\"],\"oneOf\":[{\"properties\":{\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"minItems\":1,\"uniqueItems\":true,\"description\":\"Fields that make up the primary key.\"}},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"uniqueItems\":true}}}}},{\"properties\":{\"fields\":{\"type\":\"string\",\"description\":\"Fields that make up the primary key.\"},\"reference\":{\"type\":\"object\",\"required\":[\"resource\",\"fields\"],\"properties\":{\"resource\":{\"type\":\"string\",\"default\":\"\"},\"fields\":{\"type\":\"string\"}}}}}]},\"examples\":[\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"the-resource\\\",\\n \\\"fields\\\": \\\"state_id\\\"\\n }\\n }\\n ]\\n}\\n\",\"{\\n \\\"foreignKeys\\\": [\\n {\\n \\\"fields\\\": \\\"state\\\",\\n \\\"reference\\\": {\\n \\\"resource\\\": \\\"\\\",\\n \\\"fields\\\": \\\"id\\\"\\n }\\n }\\n ]\\n}\\n\"]},\"missingValues\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"default\":[\"\"],\"description\":\"Values that when encountered in the source, should be considered as `null`, 'not present', or 'blank' values.\",\"context\":\"Many datasets arrive with missing data values, either because a value was not collected or it never existed.\\nMissing values may be indicated simply by the value being empty in other cases a special value may have been used e.g. `-`, `NaN`, `0`, `-9999` etc.\\nThe `missingValues` property provides a way to indicate that these values should be interpreted as equivalent to null.\\n\\n`missingValues` are strings rather than being the data type of the particular field. This allows for comparison prior to casting and for fields to have missing value which are not of their type, for example a `number` field to have missing values indicated by `-`.\\n\\nThe default value of `missingValue` for a non-string type field is the empty string `''`. For string type fields there is no default for `missingValue` (for string fields the empty string `''` is a valid value and need not indicate null).\",\"examples\":[\"{\\n \\\"missingValues\\\": [\\n \\\"-\\\",\\n \\\"NaN\\\",\\n \\\"\\\"\\n ]\\n}\\n\",\"{\\n \\\"missingValues\\\": []\\n}\\n\"]}},\"examples\":[\"{\\n \\\"schema\\\": {\\n \\\"fields\\\": [\\n {\\n \\\"name\\\": \\\"first_name\\\",\\n \\\"type\\\": \\\"string\\\"\\n \\\"constraints\\\": {\\n \\\"required\\\": true\\n }\\n },\\n {\\n \\\"name\\\": \\\"age\\\",\\n \\\"type\\\": \\\"integer\\\"\\n },\\n ],\\n \\\"primaryKey\\\": [\\n \\\"name\\\"\\n ]\\n }\\n}\\n\"]}},\"parameters\":{\"filterQueryParam\":{\"in\":\"query\",\"name\":\"filter\",\"schema\":{\"type\":\"string\"},\"description\":\"This is a JSON object mapping column names to arrays of allowed values. For example, to filter column `pet` for values `cat` and `dog`, the filter would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}`. JSON contains characters that are not safe to place in a URL, so it is important to url-encode them. For this example, the url-encoding is `%7B%22pet%22%3A%20%5B%22cat%22%2C%20%22dog%22%5D%7D`. See https://rosettacode.org/wiki/URL_encoding for how to url-encode a string, or https://www.urlencoder.org/ to try some examples. Multiple columns can be filtered. For example the filter for `pet` being either `cat` or `dog`, AND `size` being either `tiny` or `outrageously small`, would be `{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"], \\\"size\\\": [\\\"tiny\\\", \\\"outrageously small\\\"]}`.\",\"example\":\"{\\\"pet\\\": [\\\"cat\\\", \\\"dog\\\"]}\",\"required\":false},\"sortQueryParam\":{\"in\":\"query\",\"name\":\"sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Order in which to return results. If a single column name is given (e.g. `pet`), results are placed in ascending order of values in that column. To get results in an order that was previously prepared manually in Grist, use the special `manualSort` column name. Multiple columns can be specified, separated by commas (e.g. `pet,age`). For descending order, prefix a column name with a `-` character (e.g. `pet,-age`). To include additional sorting options append them after a colon (e.g. `pet,-age:naturalSort;emptyFirst,owner`). Available options are: `choiceOrder`, `naturalSort`, `emptyFirst`. Without the `sort` parameter, the order of results is unspecified.\",\"example\":\"pet,-age\",\"required\":false},\"limitQueryParam\":{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Return at most this number of rows. A value of 0 is equivalent to having no limit.\",\"example\":\"5\",\"required\":false},\"sortHeaderParam\":{\"in\":\"header\",\"name\":\"X-Sort\",\"schema\":{\"type\":\"string\"},\"description\":\"Same as `sort` query parameter.\",\"example\":\"pet,-age\",\"required\":false},\"limitHeaderParam\":{\"in\":\"header\",\"name\":\"X-Limit\",\"schema\":{\"type\":\"number\"},\"description\":\"Same as `limit` query parameter.\",\"example\":\"5\",\"required\":false},\"colIdPathParam\":{\"in\":\"path\",\"name\":\"colId\",\"schema\":{\"type\":\"string\"},\"description\":\"The column id (without the starting `$`) as shown in the column configuration below the label\",\"required\":true},\"tableIdPathParam\":{\"in\":\"path\",\"name\":\"tableId\",\"schema\":{\"type\":\"string\"},\"description\":\"normalized table name (see `TABLE ID` in Raw Data) or numeric row ID in `_grist_Tables`\",\"required\":true},\"docIdPathParam\":{\"in\":\"path\",\"name\":\"docId\",\"schema\":{\"type\":\"string\"},\"description\":\"A string id (UUID)\",\"required\":true},\"orgIdPathParam\":{\"in\":\"path\",\"name\":\"orgId\",\"schema\":{\"oneOf\":[{\"type\":\"integer\"},{\"type\":\"string\"}]},\"description\":\"This can be an integer id, or a string subdomain (e.g. `gristlabs`), or `current` if the org is implied by the domain in the url\",\"required\":true},\"workspaceIdPathParam\":{\"in\":\"path\",\"name\":\"workspaceId\",\"description\":\"An integer id\",\"schema\":{\"type\":\"integer\"},\"required\":true},\"userIdPathParam\":{\"in\":\"path\",\"name\":\"userId\",\"schema\":{\"type\":\"integer\"},\"description\":\"A user id\",\"required\":true},\"noparseQueryParam\":{\"in\":\"query\",\"name\":\"noparse\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to prohibit parsing strings according to the column type.\"},\"hiddenQueryParam\":{\"in\":\"query\",\"name\":\"hidden\",\"schema\":{\"type\":\"boolean\"},\"description\":\"Set to true to include the hidden columns (like \\\"manualSort\\\")\"},\"headerQueryParam\":{\"in\":\"query\",\"name\":\"header\",\"schema\":{\"type\":\"string\",\"enum\":[\"colId\",\"label\"]},\"description\":\"Format for headers. Labels tend to be more human-friendly while colIds are more normalized.\",\"required\":false}}}}},\"searchIndex\":{\"store\":[\"section/Authentication\",\"tag/orgs\",\"tag/orgs/operation/listOrgs\",\"tag/orgs/operation/describeOrg\",\"tag/orgs/operation/modifyOrg\",\"tag/orgs/operation/deleteOrg\",\"tag/orgs/operation/listOrgAccess\",\"tag/orgs/operation/modifyOrgAccess\",\"tag/workspaces\",\"tag/workspaces/operation/listWorkspaces\",\"tag/workspaces/operation/createWorkspace\",\"tag/workspaces/operation/describeWorkspace\",\"tag/workspaces/operation/modifyWorkspace\",\"tag/workspaces/operation/deleteWorkspace\",\"tag/workspaces/operation/listWorkspaceAccess\",\"tag/workspaces/operation/modifyWorkspaceAccess\",\"tag/docs\",\"tag/docs/operation/createDoc\",\"tag/docs/operation/describeDoc\",\"tag/docs/operation/modifyDoc\",\"tag/docs/operation/deleteDoc\",\"tag/docs/operation/moveDoc\",\"tag/docs/operation/listDocAccess\",\"tag/docs/operation/modifyDocAccess\",\"tag/docs/operation/downloadDoc\",\"tag/docs/operation/downloadDocXlsx\",\"tag/docs/operation/downloadDocCsv\",\"tag/docs/operation/downloadTableSchema\",\"tag/docs/operation/deleteActions\",\"tag/docs/operation/forceReload\",\"tag/records\",\"tag/records/operation/listRecords\",\"tag/records/operation/addRecords\",\"tag/records/operation/modifyRecords\",\"tag/records/operation/replaceRecords\",\"tag/tables\",\"tag/tables/operation/listTables\",\"tag/tables/operation/addTables\",\"tag/tables/operation/modifyTables\",\"tag/columns\",\"tag/columns/operation/listColumns\",\"tag/columns/operation/addColumns\",\"tag/columns/operation/modifyColumns\",\"tag/columns/operation/replaceColumns\",\"tag/columns/operation/deleteColumn\",\"tag/data\",\"tag/data/operation/getTableData\",\"tag/data/operation/addRows\",\"tag/data/operation/modifyRows\",\"tag/data/operation/deleteRows\",\"tag/attachments\",\"tag/attachments/operation/listAttachments\",\"tag/attachments/operation/uploadAttachments\",\"tag/attachments/operation/getAttachmentMetadata\",\"tag/attachments/operation/downloadAttachment\",\"tag/webhooks\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/get\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks/post\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/patch\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1{webhookId}/delete\",\"tag/webhooks/paths/~1docs~1{docId}~1webhooks~1queue/delete\",\"tag/sql\",\"tag/sql/paths/~1docs~1{docId}~1sql/get\",\"tag/sql/paths/~1docs~1{docId}~1sql/post\",\"tag/users\",\"tag/users/paths/~1users~1{userId}/delete\"],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"title\",\"description\"],\"fieldVectors\":[[\"title/0\",[0,5.15]],[\"description/0\",[1,4.48,2,3.878]],[\"title/1\",[3,2.512]],[\"description/1\",[3,1.243,4,2.206,5,1.98,6,1.98,7,2.548,8,1.811,9,2.548]],[\"title/2\",[3,1.797,10,2.002,11,2.124]],[\"description/2\",[3,1.243,4,2.206,5,1.98,6,1.98,12,2.548,13,2.548,14,2.548]],[\"title/3\",[3,2.096,15,3.338]],[\"description/3\",[16,4.103]],[\"title/4\",[3,2.096,17,2.335]],[\"description/4\",[16,4.103]],[\"title/5\",[3,2.096,18,2.476]],[\"description/5\",[16,4.103]],[\"title/6\",[3,1.573,10,1.753,11,1.859,19,1.859]],[\"description/6\",[20,4.571]],[\"title/7\",[3,1.797,11,2.124,21,2.619]],[\"description/7\",[20,4.571]],[\"title/8\",[22,2.276]],[\"description/8\",[5,2.167,8,1.982,22,1.232,23,2.789,24,2.789,25,0.681]],[\"title/9\",[3,1.399,10,1.559,22,1.268,25,0.7,26,2.868]],[\"description/9\",[27,4.571]],[\"title/10\",[22,1.628,28,2.863,29,2.863]],[\"description/10\",[27,4.571]],[\"title/11\",[15,3.338,22,1.898]],[\"description/11\",[30,4.103]],[\"title/12\",[17,2.335,22,1.898]],[\"description/12\",[30,4.103]],[\"title/13\",[18,2.476,22,1.898]],[\"description/13\",[30,4.103]],[\"title/14\",[10,1.753,11,1.859,19,1.859,22,1.425]],[\"description/14\",[31,4.571]],[\"title/15\",[11,2.124,21,2.619,22,1.628]],[\"description/15\",[31,4.571]],[\"title/16\",[32,4.002]],[\"description/16\",[22,1.361,25,0.752,33,2.393,34,2.189,35,2.393]],[\"title/17\",[25,0.9,28,2.863,29,2.863]],[\"description/17\",[36,5.281]],[\"title/18\",[15,3.338,25,1.049]],[\"description/18\",[37,4.103]],[\"title/19\",[17,1.753,25,0.787,38,2.506,39,2.122]],[\"description/19\",[37,4.103]],[\"title/20\",[18,2.476,25,1.049]],[\"description/20\",[37,4.103]],[\"title/21\",[22,1.425,25,0.787,40,3.226,41,3.226]],[\"description/21\",[42,5.281]],[\"title/22\",[10,1.753,11,1.859,19,1.859,25,0.787]],[\"description/22\",[43,4.571]],[\"title/23\",[11,2.124,21,2.619,25,0.9]],[\"description/23\",[43,4.571]],[\"title/24\",[25,0.787,39,2.122,44,3.226,45,2.293]],[\"description/24\",[46,5.281]],[\"title/25\",[25,0.787,39,2.122,45,2.293,47,3.226]],[\"description/25\",[48,5.281]],[\"title/26\",[39,2.122,45,2.293,49,0.889,50,3.226]],[\"description/26\",[51,5.281]],[\"title/27\",[49,1.185,52,3.718]],[\"description/27\",[52,2.414,53,2.789,54,2.789,55,2.789,56,2.789,57,2.789]],[\"title/28\",[58,3.226,59,2.792,60,2.792,61,3.226]],[\"description/28\",[62,5.281]],[\"title/29\",[25,1.049,63,4.296]],[\"description/29\",[25,0.573,64,2.346,65,2.346,66,2.346,67,2.346,68,2.346,69,2.346,70,2.346]],[\"title/30\",[71,2.389]],[\"description/30\",[8,1.982,33,2.167,34,1.982,49,0.769,71,1.294,72,1.982]],[\"title/31\",[49,1.016,71,1.709,73,3.189]],[\"description/31\",[74,3.754]],[\"title/32\",[49,1.016,71,1.709,75,2.262]],[\"description/32\",[74,3.754]],[\"title/33\",[17,2.002,49,1.016,71,1.709]],[\"description/33\",[74,3.754]],[\"title/34\",[49,0.889,71,1.496,75,1.981,76,2.792]],[\"description/34\",[74,3.754]],[\"title/35\",[49,1.42]],[\"description/35\",[25,0.839,34,2.444,49,0.948,77,2.975]],[\"title/36\",[10,2.002,25,0.9,49,1.016]],[\"description/36\",[78,4.103]],[\"title/37\",[25,0.9,49,1.016,75,2.262]],[\"description/37\",[78,4.103]],[\"title/38\",[17,2.002,25,0.9,49,1.016]],[\"description/38\",[78,4.103]],[\"title/39\",[79,2.799]],[\"description/39\",[34,2.444,49,0.948,77,2.975,79,1.868]],[\"title/40\",[10,2.002,49,1.016,79,2.002]],[\"description/40\",[80,3.754]],[\"title/41\",[49,1.016,75,2.262,79,2.002]],[\"description/41\",[80,3.754]],[\"title/42\",[17,2.002,49,1.016,79,2.002]],[\"description/42\",[80,3.754]],[\"title/43\",[49,0.889,75,1.981,76,2.792,79,1.753]],[\"description/43\",[80,3.754]],[\"title/44\",[18,2.124,49,1.016,79,2.002]],[\"description/44\",[81,5.281]],[\"title/45\",[82,3.389]],[\"description/45\",[49,0.491,71,0.826,82,1.172,83,1.781,84,1.383,85,2.936,86,1.266,87,1.781,88,1.781,89,1.781,90,1.093]],[\"title/46\",[49,1.016,73,3.189,82,2.424]],[\"description/46\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/47\",[49,1.016,72,2.619,75,2.262]],[\"description/47\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/48\",[17,2.002,49,1.016,72,2.619]],[\"description/48\",[71,1.136,86,1.02,90,1.504,91,1.115,92,1.115,93,1.115,94,1.02,95,1.115,96,1.115,97,1.115,98,1.115,99,1.02,100,1.115,101,1.115]],[\"title/49\",[18,2.124,49,1.016,72,2.619]],[\"description/49\",[102,5.281]],[\"title/50\",[103,3.163]],[\"description/50\",[25,0.463,45,1.347,71,0.879,79,1.03,82,1.247,84,1.472,103,1.897,104,1.895,105,1.895,106,1.895]],[\"title/51\",[10,1.753,32,2.506,38,2.506,103,1.981]],[\"description/51\",[107,4.571]],[\"title/52\",[32,2.863,103,2.262,108,3.685]],[\"description/52\",[107,4.571]],[\"title/53\",[38,3.338,103,2.638]],[\"description/53\",[109,5.281]],[\"title/54\",[39,2.424,103,2.262,110,3.685]],[\"description/54\",[111,5.281]],[\"title/55\",[112,3.163]],[\"description/55\",[8,1.811,21,1.811,25,0.622,112,1.565,113,2.548,114,2.548,115,2.548]],[\"title/56\",[25,0.9,112,2.262,116,3.685]],[\"description/56\",[117,4.571]],[\"title/57\",[25,0.787,28,2.506,99,2.293,112,1.981]],[\"description/57\",[117,4.571]],[\"title/58\",[17,2.335,112,2.638]],[\"description/58\",[118,4.571]],[\"title/59\",[94,3.054,112,2.638]],[\"description/59\",[118,4.571]],[\"title/60\",[29,2.229,59,2.483,119,2.868,120,2.868,121,2.868]],[\"description/60\",[122,5.281]],[\"title/61\",[123,3.661]],[\"description/61\",[25,0.752,82,2.026,90,1.891,123,2.189,124,2.393]],[\"title/62\",[25,0.7,123,2.039,124,2.229,125,2.483,126,2.483]],[\"description/62\",[127,4.571]],[\"title/63\",[25,0.573,123,1.669,124,1.824,125,2.032,126,2.032,128,2.348,129,2.348]],[\"description/63\",[127,4.571]],[\"title/64\",[19,2.969]],[\"description/64\",[19,2.582,35,3.481]],[\"title/65\",[18,2.124,19,2.124,35,2.863]],[\"description/65\",[2,1.643,6,0.832,18,1.094,19,0.617,22,0.473,25,0.261,33,0.832,60,1.643,84,0.832,90,0.658,130,1.071,131,1.071,132,1.071,133,1.071,134,1.071,135,1.071,136,1.071,137,1.071,138,1.071,139,1.071]]],\"invertedIndex\":[[\"\",{\"_index\":2,\"title\":{},\"description\":{\"0\":{},\"65\":{}}}],[\"access\",{\"_index\":11,\"title\":{\"2\":{},\"6\":{},\"7\":{},\"14\":{},\"15\":{},\"22\":{},\"23\":{}},\"description\":{}}],[\"account\",{\"_index\":135,\"title\":{},\"description\":{\"65\":{}}}],[\"action\",{\"_index\":60,\"title\":{\"28\":{}},\"description\":{\"65\":{}}}],[\"add\",{\"_index\":75,\"title\":{\"32\":{},\"34\":{},\"37\":{},\"41\":{},\"43\":{},\"47\":{}},\"description\":{}}],[\"against\",{\"_index\":126,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"allow\",{\"_index\":134,\"title\":{},\"description\":{\"65\":{}}}],[\"anoth\",{\"_index\":41,\"title\":{\"21\":{}},\"description\":{}}],[\"api\",{\"_index\":9,\"title\":{},\"description\":{\"1\":{}}}],[\"area\",{\"_index\":13,\"title\":{},\"description\":{\"2\":{}}}],[\"associ\",{\"_index\":116,\"title\":{\"56\":{}},\"description\":{}}],[\"attach\",{\"_index\":103,\"title\":{\"50\":{},\"51\":{},\"52\":{},\"53\":{},\"54\":{}},\"description\":{\"50\":{}}}],[\"authent\",{\"_index\":0,\"title\":{\"0\":{}},\"description\":{}}],[\"avail\",{\"_index\":14,\"title\":{},\"description\":{\"2\":{}}}],[\"better\",{\"_index\":96,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"call\",{\"_index\":8,\"title\":{},\"description\":{\"1\":{},\"8\":{},\"30\":{},\"55\":{}}}],[\"cautiou\",{\"_index\":138,\"title\":{},\"description\":{\"65\":{}}}],[\"chang\",{\"_index\":21,\"title\":{\"7\":{},\"15\":{},\"23\":{}},\"description\":{\"55\":{}}}],[\"close\",{\"_index\":64,\"title\":{},\"description\":{\"29\":{}}}],[\"collect\",{\"_index\":34,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"35\":{},\"39\":{}}}],[\"column\",{\"_index\":79,\"title\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}},\"description\":{\"39\":{},\"50\":{}}}],[\"columnar\",{\"_index\":87,\"title\":{},\"description\":{\"45\":{}}}],[\"consid\",{\"_index\":95,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"contain\",{\"_index\":33,\"title\":{},\"description\":{\"16\":{},\"30\":{},\"65\":{}}}],[\"content\",{\"_index\":39,\"title\":{\"19\":{},\"24\":{},\"25\":{},\"26\":{},\"54\":{}},\"description\":{}}],[\"creat\",{\"_index\":28,\"title\":{\"10\":{},\"17\":{},\"57\":{}},\"description\":{}}],[\"csv\",{\"_index\":50,\"title\":{\"26\":{}},\"description\":{}}],[\"current\",{\"_index\":132,\"title\":{},\"description\":{\"65\":{}}}],[\"data\",{\"_index\":82,\"title\":{\"45\":{},\"46\":{}},\"description\":{\"45\":{},\"50\":{},\"61\":{}}}],[\"delet\",{\"_index\":18,\"title\":{\"5\":{},\"13\":{},\"20\":{},\"44\":{},\"49\":{},\"65\":{}},\"description\":{\"65\":{}}}],[\"deprec\",{\"_index\":86,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{}}}],[\"describ\",{\"_index\":15,\"title\":{\"3\":{},\"11\":{},\"18\":{}},\"description\":{}}],[\"doc\",{\"_index\":32,\"title\":{\"16\":{},\"51\":{},\"52\":{}},\"description\":{}}],[\"docs/{docid\",{\"_index\":37,\"title\":{},\"description\":{\"18\":{},\"19\":{},\"20\":{}}}],[\"docs/{docid}/access\",{\"_index\":43,\"title\":{},\"description\":{\"22\":{},\"23\":{}}}],[\"docs/{docid}/attach\",{\"_index\":107,\"title\":{},\"description\":{\"51\":{},\"52\":{}}}],[\"docs/{docid}/attachments/{attachmentid\",{\"_index\":109,\"title\":{},\"description\":{\"53\":{}}}],[\"docs/{docid}/attachments/{attachmentid}/download\",{\"_index\":111,\"title\":{},\"description\":{\"54\":{}}}],[\"docs/{docid}/download\",{\"_index\":46,\"title\":{},\"description\":{\"24\":{}}}],[\"docs/{docid}/download/csv\",{\"_index\":51,\"title\":{},\"description\":{\"26\":{}}}],[\"docs/{docid}/download/table-schema\",{\"_index\":57,\"title\":{},\"description\":{\"27\":{}}}],[\"docs/{docid}/download/xlsx\",{\"_index\":48,\"title\":{},\"description\":{\"25\":{}}}],[\"docs/{docid}/force-reload\",{\"_index\":70,\"title\":{},\"description\":{\"29\":{}}}],[\"docs/{docid}/mov\",{\"_index\":42,\"title\":{},\"description\":{\"21\":{}}}],[\"docs/{docid}/sql\",{\"_index\":127,\"title\":{},\"description\":{\"62\":{},\"63\":{}}}],[\"docs/{docid}/states/remov\",{\"_index\":62,\"title\":{},\"description\":{\"28\":{}}}],[\"docs/{docid}/t\",{\"_index\":78,\"title\":{},\"description\":{\"36\":{},\"37\":{},\"38\":{}}}],[\"docs/{docid}/tables/{tableid}/column\",{\"_index\":80,\"title\":{},\"description\":{\"40\":{},\"41\":{},\"42\":{},\"43\":{}}}],[\"docs/{docid}/tables/{tableid}/columns/{colid\",{\"_index\":81,\"title\":{},\"description\":{\"44\":{}}}],[\"docs/{docid}/tables/{tableid}/data\",{\"_index\":101,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"docs/{docid}/tables/{tableid}/data/delet\",{\"_index\":102,\"title\":{},\"description\":{\"49\":{}}}],[\"docs/{docid}/tables/{tableid}/record\",{\"_index\":74,\"title\":{},\"description\":{\"31\":{},\"32\":{},\"33\":{},\"34\":{}}}],[\"docs/{docid}/webhook\",{\"_index\":117,\"title\":{},\"description\":{\"56\":{},\"57\":{}}}],[\"docs/{docid}/webhooks/queu\",{\"_index\":122,\"title\":{},\"description\":{\"60\":{}}}],[\"docs/{docid}/webhooks/{webhookid\",{\"_index\":118,\"title\":{},\"description\":{\"58\":{},\"59\":{}}}],[\"document\",{\"_index\":25,\"title\":{\"9\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"29\":{},\"36\":{},\"37\":{},\"38\":{},\"56\":{},\"57\":{},\"62\":{},\"63\":{}},\"description\":{\"8\":{},\"16\":{},\"29\":{},\"35\":{},\"50\":{},\"55\":{},\"61\":{},\"65\":{}}}],[\"document'\",{\"_index\":59,\"title\":{\"28\":{},\"60\":{}},\"description\":{}}],[\"download\",{\"_index\":110,\"title\":{\"54\":{}},\"description\":{}}],[\"empti\",{\"_index\":29,\"title\":{\"10\":{},\"17\":{},\"60\":{}},\"description\":{}}],[\"endpoint\",{\"_index\":90,\"title\":{},\"description\":{\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"61\":{},\"65\":{}}}],[\"engin\",{\"_index\":68,\"title\":{},\"description\":{\"29\":{}}}],[\"enumer\",{\"_index\":12,\"title\":{},\"description\":{\"2\":{}}}],[\"excel\",{\"_index\":47,\"title\":{\"25\":{}},\"description\":{}}],[\"favor\",{\"_index\":91,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"fetch\",{\"_index\":73,\"title\":{\"31\":{},\"46\":{}},\"description\":{}}],[\"file\",{\"_index\":45,\"title\":{\"24\":{},\"25\":{},\"26\":{}},\"description\":{\"50\":{}}}],[\"follow\",{\"_index\":53,\"title\":{},\"description\":{\"27\":{}}}],[\"forc\",{\"_index\":66,\"title\":{},\"description\":{\"29\":{}}}],[\"format\",{\"_index\":88,\"title\":{},\"description\":{\"45\":{}}}],[\"frictionlessdata'\",{\"_index\":54,\"title\":{},\"description\":{\"27\":{}}}],[\"grist\",{\"_index\":35,\"title\":{\"65\":{}},\"description\":{\"16\":{},\"64\":{}}}],[\"group\",{\"_index\":24,\"title\":{},\"description\":{\"8\":{}}}],[\"histori\",{\"_index\":61,\"title\":{\"28\":{}},\"description\":{}}],[\"immedi\",{\"_index\":92,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"includ\",{\"_index\":104,\"title\":{},\"description\":{\"50\":{}}}],[\"list\",{\"_index\":10,\"title\":{\"2\":{},\"6\":{},\"9\":{},\"14\":{},\"22\":{},\"36\":{},\"40\":{},\"51\":{}},\"description\":{}}],[\"metadata\",{\"_index\":38,\"title\":{\"19\":{},\"51\":{},\"53\":{}},\"description\":{}}],[\"modifi\",{\"_index\":17,\"title\":{\"4\":{},\"12\":{},\"19\":{},\"33\":{},\"38\":{},\"42\":{},\"48\":{},\"58\":{}},\"description\":{}}],[\"move\",{\"_index\":40,\"title\":{\"21\":{}},\"description\":{}}],[\"new\",{\"_index\":99,\"title\":{\"57\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"now\",{\"_index\":85,\"title\":{},\"description\":{\"45\":{}}}],[\"option\",{\"_index\":128,\"title\":{\"63\":{}},\"description\":{}}],[\"org\",{\"_index\":3,\"title\":{\"1\":{},\"2\":{},\"3\":{},\"4\":{},\"5\":{},\"6\":{},\"7\":{},\"9\":{}},\"description\":{\"1\":{},\"2\":{}}}],[\"organ\",{\"_index\":23,\"title\":{},\"description\":{\"8\":{}}}],[\"organis\",{\"_index\":131,\"title\":{},\"description\":{\"65\":{}}}],[\"orgs/{orgid\",{\"_index\":16,\"title\":{},\"description\":{\"3\":{},\"4\":{},\"5\":{}}}],[\"orgs/{orgid}/access\",{\"_index\":20,\"title\":{},\"description\":{\"6\":{},\"7\":{}}}],[\"orgs/{orgid}/workspac\",{\"_index\":27,\"title\":{},\"description\":{\"9\":{},\"10\":{}}}],[\"paramet\",{\"_index\":129,\"title\":{\"63\":{}},\"description\":{}}],[\"payload\",{\"_index\":121,\"title\":{\"60\":{}},\"description\":{}}],[\"person\",{\"_index\":6,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"65\":{}}}],[\"plan\",{\"_index\":93,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"pleas\",{\"_index\":137,\"title\":{},\"description\":{\"65\":{}}}],[\"point\",{\"_index\":98,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"project\",{\"_index\":100,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"python\",{\"_index\":67,\"title\":{},\"description\":{\"29\":{}}}],[\"queri\",{\"_index\":124,\"title\":{\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"queue\",{\"_index\":119,\"title\":{\"60\":{}},\"description\":{}}],[\"recommend\",{\"_index\":89,\"title\":{},\"description\":{\"45\":{}}}],[\"record\",{\"_index\":71,\"title\":{\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{}},\"description\":{\"30\":{},\"45\":{},\"46\":{},\"47\":{},\"48\":{},\"50\":{}}}],[\"refer\",{\"_index\":105,\"title\":{},\"description\":{\"50\":{}}}],[\"reload\",{\"_index\":63,\"title\":{\"29\":{}},\"description\":{}}],[\"remov\",{\"_index\":94,\"title\":{\"59\":{}},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"reopen\",{\"_index\":65,\"title\":{},\"description\":{\"29\":{}}}],[\"request\",{\"_index\":114,\"title\":{},\"description\":{\"55\":{}}}],[\"restart\",{\"_index\":69,\"title\":{},\"description\":{\"29\":{}}}],[\"row\",{\"_index\":72,\"title\":{\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{}}}],[\"run\",{\"_index\":125,\"title\":{\"62\":{},\"63\":{}},\"description\":{}}],[\"schema\",{\"_index\":52,\"title\":{\"27\":{}},\"description\":{\"27\":{}}}],[\"securitydefinit\",{\"_index\":1,\"title\":{},\"description\":{\"0\":{}}}],[\"site\",{\"_index\":5,\"title\":{},\"description\":{\"1\":{},\"2\":{},\"8\":{}}}],[\"space\",{\"_index\":7,\"title\":{},\"description\":{\"1\":{}}}],[\"sql\",{\"_index\":123,\"title\":{\"61\":{},\"62\":{},\"63\":{}},\"description\":{\"61\":{}}}],[\"sqlite\",{\"_index\":44,\"title\":{\"24\":{}},\"description\":{}}],[\"standard](https://specs.frictionlessdata.io/table-schema\",{\"_index\":56,\"title\":{},\"description\":{\"27\":{}}}],[\"start\",{\"_index\":97,\"title\":{},\"description\":{\"46\":{},\"47\":{},\"48\":{}}}],[\"structur\",{\"_index\":77,\"title\":{},\"description\":{\"35\":{},\"39\":{}}}],[\"tabl\",{\"_index\":49,\"title\":{\"26\":{},\"27\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{},\"38\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"46\":{},\"47\":{},\"48\":{},\"49\":{}},\"description\":{\"30\":{},\"35\":{},\"39\":{},\"45\":{}}}],[\"table-schema\",{\"_index\":55,\"title\":{},\"description\":{\"27\":{}}}],[\"team\",{\"_index\":4,\"title\":{},\"description\":{\"1\":{},\"2\":{}}}],[\"themselv\",{\"_index\":133,\"title\":{},\"description\":{\"65\":{}}}],[\"trigger\",{\"_index\":113,\"title\":{},\"description\":{\"55\":{}}}],[\"truncat\",{\"_index\":58,\"title\":{\"28\":{}},\"description\":{}}],[\"type\",{\"_index\":106,\"title\":{},\"description\":{\"50\":{}}}],[\"undeliv\",{\"_index\":120,\"title\":{\"60\":{}},\"description\":{}}],[\"undon\",{\"_index\":136,\"title\":{},\"description\":{\"65\":{}}}],[\"updat\",{\"_index\":76,\"title\":{\"34\":{},\"43\":{}},\"description\":{}}],[\"upload\",{\"_index\":108,\"title\":{\"52\":{}},\"description\":{}}],[\"url\",{\"_index\":115,\"title\":{},\"description\":{\"55\":{}}}],[\"us\",{\"_index\":84,\"title\":{},\"description\":{\"45\":{},\"50\":{},\"65\":{}}}],[\"user\",{\"_index\":19,\"title\":{\"6\":{},\"14\":{},\"22\":{},\"64\":{},\"65\":{}},\"description\":{\"64\":{},\"65\":{}}}],[\"user'\",{\"_index\":130,\"title\":{},\"description\":{\"65\":{}}}],[\"users/{userid\",{\"_index\":139,\"title\":{},\"description\":{\"65\":{}}}],[\"webhook\",{\"_index\":112,\"title\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{}},\"description\":{\"55\":{}}}],[\"within\",{\"_index\":26,\"title\":{\"9\":{}},\"description\":{}}],[\"work\",{\"_index\":83,\"title\":{},\"description\":{\"45\":{}}}],[\"workspac\",{\"_index\":22,\"title\":{\"8\":{},\"9\":{},\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"21\":{}},\"description\":{\"8\":{},\"16\":{},\"65\":{}}}],[\"workspaces/{workspaceid\",{\"_index\":30,\"title\":{},\"description\":{\"11\":{},\"12\":{},\"13\":{}}}],[\"workspaces/{workspaceid}/access\",{\"_index\":31,\"title\":{},\"description\":{\"14\":{},\"15\":{}}}],[\"workspaces/{workspaceid}/doc\",{\"_index\":36,\"title\":{},\"description\":{\"17\":{}}}]],\"pipeline\":[]}},\"options\":{\"theme\":{\"spacing\":{\"sectionVertical\":2},\"breakpoints\":{\"medium\":\"50rem\",\"large\":\"50rem\"},\"sidebar\":{\"width\":\"0px\"}},\"hideDownloadButton\":true,\"pathInMiddlePanel\":true,\"scrollYOffset\":48,\"jsonSampleExpandLevel\":\"all\"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);","title":"REST API reference"},{"location":"api/#grist-api-reference","text":"REST API for manipulating documents, workspaces, and team sites. API Usage is an introduction to using the API. API Console allows you to make API calls from the browser. Authentication orgs get List the orgs you have access to get Describe an org patch Modify an org del Delete an org get List users with access to org patch Change who has access to org workspaces get List workspaces and documents within an org post Create an empty workspace get Describe a workspace patch Modify a workspace del Delete a workspace get List users with access to workspace patch Change who has access to workspace docs post Create an empty document get Describe a document patch Modify document metadata (but not its contents) del Delete a document patch Move document to another workspace. get List users with access to document patch Change who has access to document get Content of document, as an Sqlite file get Content of document, as an Excel file get Content of table, as a CSV file get The schema of a table post Truncate the document's action history post Reload a document records get Fetch records from a table post Add records to a table patch Modify records of a table put Add or update records of a table tables get List tables in a document post Add tables to a document patch Modify tables of a document columns get List columns in a table post Add columns to a table patch Modify columns of a table put Add or update columns of a table del Delete a column of a table data get Fetch data from a table post Add rows to a table patch Modify rows of a table post Delete rows of a table attachments get List metadata of all attachments in a doc post Upload attachments to a doc get Get the metadata for an attachment get Download the contents of an attachment webhooks get Webhooks associated with a document post Create new webhooks for a document patch Modify a webhook del Remove a webhook del Empty a document's queue of undelivered payloads sql get Run an SQL query against a document post Run an SQL query against a document, with options or parameters users del Delete a user from Grist API docs by Redocly","title":"Grist API Reference"},{"location":"integrators/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Integrator Services # Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make Configuring Integrators # Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables. Example: Storing form submissions # Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in! Example: Sending email alerts # We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win! Readiness column # Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example . Triggering (or avoiding triggering) on pre-existing records # The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Integrator services"},{"location":"integrators/#integrator-services","text":"Grist can be connected to thousands of other services via integrators with Grist support. These include: Zapier Integrately Pabbly Connect KonnectzIT n8n Make","title":"Integrator Services"},{"location":"integrators/#configuring-integrators","text":"Each integrator provides its own way to configure the connection between Grist and other services. Pabbly Connect has created a few videos walking through how to set up an integration with Grist using Pabbly Connect. Pabbly Connect Youtube - Grist Playlist Included below is a walkthrough of an example of how an integration with Grist can be configured using Zapier . Grist can trigger a workflow whenever there is a new or updated record in a table, leading to action in another service. Conversely, workflows triggered by other services can consult, add, or update records in Grist tables.","title":"Configuring Integrators"},{"location":"integrators/#example-storing-form-submissions","text":"Suppose we have a form for collecting votes on the color of a proposed new bike shed: The form is set up using Google Forms (for this example), and we want the responses to be stored in a Grist document: One way to make this happen is with Zapier . So let\u2019s sign in on the Zapier site and then visit the Grist integration page : We\u2019d like to pair Grist with Google forms. Zapier supports several form providers, and the overall process for integration is similar for them all. Just type in the provider you want. For this tutorial, we\u2019re going with Google forms. Once we\u2019ve picked the provider to integrate with, we need to pin down exactly what we want it to do, from the available \u201ctriggers\u201d and \u201cactions.\u201d In this case, we choose that when there is a New Response in Spreadsheet trigger for Google Forms, we will do the Create Record action in Grist. We click the build button to start filling in the details: Since the triggering event for the integration will happen in Google Forms, we are first asked to give Zapier some access rights to your forms. Once that is done, we are prompted to confirm which spreadsheet to use: Then we specify which worksheet within the spreadsheet to use (easy if there is just one). For Zapier\u2019s benefit in a later step, it is important that there be at least one sample response already in the spreadsheet. That\u2019s the Google side done. Now for the Grist side. We are prompted to give an API key for Grist, so we set up an API key if we haven\u2019t already. To give precise access rights, we could set up a user account just for the integration, and give it access to just what it needs, and supply its API key. Now we confirm the team to use - personal docs or a team site we have access to: Then we pick the Grist document to send form responses to: And then we pick the table to use within that document. It should have columns to store whatever parts of the form we want to keep. It is important to make this table if it doesn\u2019t exist already; it won\u2019t be created automatically. It isn\u2019t important to match column names with questions. Zapier allows for flexible mapping of fields between services. In our case, a one-to-one mapping works fine: Ok! Now we can click our button to have Zap test our integration. All going well, we can turn the \u201cZap\u201d on and leave it run. Now is the time to try making some submissions, and go have a cup of something. Free \u201cZaps\u201d may run periodically to check for new submissions, so don\u2019t expect immediate results in all cases. But eventually, you should see the votes pouring in!","title":"Example: Storing form submissions"},{"location":"integrators/#example-sending-email-alerts","text":"We\u2019ve seen an example of an outside service sending data to Grist. Now let\u2019s look at an example of Grist sending data to an outside service. Continuing our form example, where a Grist document is accumulating votes for a preferred color: now suppose that every time a new vote comes in we want to send an email summarizing which option is in the lead. We write a formula to prepare the text in a Text cell: Let\u2019s return again to the Grist integration page on Zapier. There are several mail integrations. For this example, we pick Gmail: Once we\u2019ve picked the service to connect, now we choose exactly what we want it to do. In this case, we choose that when there is a New or Updated Record (Instant) in Grist, we will Send Email in Gmail. Note the Instant there. Triggers in Zapier can be either a regular kind where Zapier periodically checks for changes (this is relatively slow), or a special \u201cinstant\u201d kind that needs special support from the triggering service but is a lot faster. Grist supports either kind of trigger, and we strongly recommend \u201cinstant\u201d if you prefer results in seconds rather than minutes, and especially if you are the sort to get anxious if someone doesn\u2019t respond to your IMs immediately. Once we\u2019ve chosen a Grist account to use as before, we can pick a table within a document to monitor. For instant triggers, we can optionally specify a \u201creadiness\u201d column . If we leave this blank, anytime a record is created or changed in the selected table, Grist will notify Zapier about it. If we set it, it should generally be to a toggle column , and Grist will notify Zapier only for records when that column is turned on. That is handy for records that have many columns being filled in manually, when we don\u2019t want to trigger until they are complete. For this example, it is fine to leave the readiness column blank. (For regular non-instant triggers, we would need to pick a specific column to monitor. Ideally this would be an Updated At column, see Timestamp columns ). On the Gmail side, we can email to pre-set addresses, or this could be configured dynamically (we\u2019ll see an example of how in a moment): We choose to set the body of the email to contain \u201cCustom\u201d content, in this case the Text cell we calculated earlier. And we\u2019re done! Zapier will offer to make a quick test that emails go out correctly: Then you can make some votes and watch the system work. For instant triggers, results should show up fairly snappily. Otherwise, Zapier has a \u201crun zap\u201d functionality to force an integration to update immediately: And emails should start showing up in the desired inboxes. May the best almost indistinguishable shade win!","title":"Example: Sending email alerts"},{"location":"integrators/#readiness-column","text":"Grist has a mechanism for alerting other services when data changes within a document. This serves as the basis for Zapier instant triggers. Since Grist is a spreadsheet, it is common for records (rows) to be created empty, and for cells to then be filled in one by one. This creates an important nuance for notifications. Usually it won\u2019t be desirable to send a notification until the record is in some sense \u201cready\u201d, but when exactly is that? Grist lets the user decide for themselves, by creating a toggle (boolean) column which is turned on when the record is ready. The column can be set manually, or via a formula. This is called a readiness column. For example, if you only want to activate a trigger when columns called Name and Email are not empty, your readiness column can have the following formula: bool($Name and $Email) You would make the column take effect by supplying it in the Readiness column option described in the email alert example .","title":"Readiness column"},{"location":"integrators/#triggering-or-avoiding-triggering-on-pre-existing-records","text":"The order of steps matters when setting up an integration that uses a readiness column. If you have existing data, think through whether you want the integration to affect all existing data or just updates and new data. For example, if you are sending data from Grist to Google Sheets using a Zapier integration, you\u2019ll probably want to send your existing data. In this case, set up and enable your Zap first with an empty readiness column, then turn on all the readiness cells. If you want to send a notification only when something is added to Grist, and not for pre-existing records, make sure your readiness cells are all turned on prior to enabling the integration, otherwise once they are turned on notifications will be sent for all of them. That may be a lot!","title":"Triggering (or avoiding triggering) on pre-existing records"},{"location":"embedding/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Embedding Grist # Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view: Parameters # Read-Only vs. Editable # Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing . Appearance # Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Embedding"},{"location":"embedding/#embedding-grist","text":"Perhaps you\u2019d like to list your product, prices and quantities on your website, or you want to display a pie chart of voting results that updates live. With public access turned on, you may embed your Grist document on your own site. To do that, you first need to make it public and have access to your website\u2019s code in order to place some HTML code in the desired location. If your site is hosted on some popular cloud CMS platform (like Blogger or WordPress), you may find HTML code blocks in your theme editor, or may need to use a plugin to access your site\u2019s code. If you have any problems editing your site, feel free to ask us or post a question on our Community Forum . Once you have decided where to embed your document, paste this code snippet in your HTML file: The src attribute points to the URL of the page you want to embed. To get the URL for your document, simply navigate to the page you want to share and copy the URL from the browser\u2019s address bar. Appending the ?embed=true parameter at the end tells Grist that it should show only the content of your page, removing any elements that are not necessary for the embedded version. You may wish to adjust height and width attributes to make it look better on your site. Since this Help Center document is a regular HTML file, we can try it right away! Below you should be able to see an embedded live table (not a screenshot) from one of our examples : This is a live, read-only view of the Grist page and it gets updated as soon as someone edits it. You can, of course, embed any page you wish, including card view, charts and any page with multiple sections. Here are two more examples with a chart and a card list view:","title":"Embedding Grist"},{"location":"embedding/#parameters","text":"","title":"Parameters"},{"location":"embedding/#read-only-vs-editable","text":"Appending a URL with ?embed=true shares the page as read-only while ?style=singlePage can be edited and follows access rules . Sharing an Embedded Style View Adding ?embed=true or ?style=singlePage to the end of a document\u2019s page URL renders the document without the toolbar on top, the page menu on the left or creator panel on the right. To learn more about link sharing, see Public access and link sharing .","title":"Read-Only vs. Editable"},{"location":"embedding/#appearance","text":"Control how an embedded iframe appears by assigning a theme appearance by appending ?themeAppearance=light for light mode or ?themeAppearance=dark for dark mode to your URL. You can also override the operating system\u2019s default using ?themeSyncWithOs=false . Chaining Parameters When adding parameters to the end of your URL, the first always leads with ? . Any additional parameters lead with & . For example, https://templates.getgrist.com/6D8E2h2DQNwS/Task-Management/p/6?embed=true&themeAppearance=dark&themeSyncWithOs=false creates an embedded-style, read-only view and forces dark mode, regardless of your OS settings.","title":"Appearance"},{"location":"webhooks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . description: How to configure webhooks for some external integrations # Webhooks # Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document. Configuration # Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address. Security # In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy. Payloads # When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together. Error conditions # If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully. Webhook queue # Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhooks"},{"location":"webhooks/#description-how-to-configure-webhooks-for-some-external-integrations","text":"","title":"description: How to configure webhooks for some external integrations"},{"location":"webhooks/#webhooks","text":"Webhooks enable you to notify external services whenever rows are added to a table or if existing rows are modified. You can configure webhooks from the \u2018Document Settings\u2019 page. Click \u2018Settings\u2019 under the \u2018Tools\u2019 menu found at the bottom of the left-hand navigation panel while viewing a document. Under the \u2018API\u2019 section of \u2018Document Settings\u2019, click on the \u2018Manage Webhooks\u2019 button. This will enable you to define webhooks for your document, where each card in this settings page represents a webhook for your document.","title":"Webhooks"},{"location":"webhooks/#configuration","text":"Each webhook has several fields. Some fields are defined by the user, and other fields are read-only and used to record information about the processing of the webhook. Not all fields are required. Name : A short, descriptive name given to the webhook. Memo (optional): A longer description of the webhook\u2019s purpose. Event types : Whether adding or modifying rows triggers a webhook. Table : The table that will trigger this webhook. Filter for changes in these columns (optional): A semicolon-separated list of column IDs. If an existing row is edited, the webhook will trigger only if one of the filtered columns was changed, and if the webhook is configured to trigger on modification. If a webhook triggers when adding a row, it does not matter which columns are defined when the new row is added. Ready column (optional): A boolean, or Toggle , column on the table that determines if the row should trigger the webhook or not. When the column becomes true, the corresponding row will trigger the webhook. URL : The remote URL of the service that the webhook will notify of added or changed rows. When self-hosting, only external services listed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. Note that there are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Header Authorization (optional): Credentials to be supplied to the webhook endpoint in the Authorization HTTP header. Not all endpoints require credentials. This is a static string. A common usage of this field is to provide an API token as required by the webhook\u2019s URL. Enabled : Whether the webhook should monitor its configured table for changes or not. If the webhook is disabled, no changes to its table will trigger it. The following fields are informational and read-only: Webhook id : An automatic, internally-generated unique ID for the webhook. Status : A JSON object that summarises the current status of the webhook, as well as the results regarding the number of times it has been invoked. This includes any potential error messages or statuses the webhook may have received when attempting to send a payload to the remote address.","title":"Configuration"},{"location":"webhooks/#security","text":"In untrusted self-hosted environments , the internal Grist endpoints can be exposed if any user is allowed to create documents and configure webhooks. There are two ways to mitigate this risk: Use the ALLOWED_WEBHOOK_DOMAINS environment variable to list the allowed domains that webhooks can use. Use the GRIST_HTTPS_PROXY environment variable to restrict webhook invocations to go through that proxy, along with setting ALLOWED_WEBHOOK_DOMAINS=* . In this way, all domains are allowed, but the webhooks will send requests through the configured proxy, thus safeguarding your internal Grist endpoints. This is the configuration that Grist Labs uses in our cloud-hosted environment. In a trusted environment where malicious users are not expected to exist, setting ALLOWED_WEBHOOK_DOMAINS=* alone may be sufficient without needing to configure a proxy.","title":"Security"},{"location":"webhooks/#payloads","text":"When a webhook is triggered, the rows that matched the webhook\u2019s conditions will generate a JSON array as the webhook\u2019s payload. Here is an example payload. [ { \"id\": 29, \"manualSort\": 29, \"Title\": \"Trophy\", \"URL\": \"https://example.com/buy/Trophy\", \"Price\": 60, \"Purchase_status\": \"wishlisted\", \"Currency\": \"USD\", \"Play_status\": \"Not started\", \"Box_art\": null, \"Price_CAD_\": 82.362 }, { \"id\": 24, \"manualSort\": 24, \"Title\": \"Dataman\", \"URL\": \"https://example.com/buy/Dataman\", \"Price\": 50, \"Purchase_status\": \"own digitally\", \"Currency\": \"EUR\", \"Play_status\": \"Finished\", \"Box_art\": null, \"Price_CAD_\": 74.71 } ] Multiple rows can simultaneously trigger the same webhook. In that case, those rows will be sent together in the same payload. The \u2018Ready Column\u2019 in the webhook\u2019s configuration can be useful for gating which rows should be sent together.","title":"Payloads"},{"location":"webhooks/#error-conditions","text":"If a webhook fails to deliver its payload to the specified URL, it will keep retrying periodically. The \u2018Status\u2019 column in the webhook configuration can be useful for diagnosing any such problems, or to verify that the payloads were delivered successfully.","title":"Error conditions"},{"location":"webhooks/#webhook-queue","text":"Webhook payloads are delivered according to a batched queue. Attempts to reach an endpoint are removed from the queue when a successful delivery happens. In case there is a problem with the webhook configuration, it can be helpful to purge the webhook\u2019s queue. The \u2018Clear Queue\u2019 button in the webhook\u2019s configuration page will remove any pending invocations of the webhook and discard the associated payloads.","title":"Webhook queue"},{"location":"code/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Plugin API # The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Intro to Plugin API"},{"location":"code/#plugin-api","text":"The plugin API is used in custom widgets to interact with the document within which the widget is embedded. If you haven\u2019t already, be sure to read our summary of Custom Widgets and look at the grist-widget repository for examples of widget implementations. Here you can find a reference for functions and interfaces available to custom widgets, starting with the grist object.","title":"Plugin API"},{"location":"code/modules/grist_plugin_api/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Module: grist-plugin-api # Table of contents # Interfaces # AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap Type Aliases # ColumnsToMap UIRowId Variables # checkers docApi sectionApi selectedTable viewApi widgetApi Functions # allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows Type Aliases # ColumnsToMap # \u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget. UIRowId # \u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row. Variables # checkers # \u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above. docApi # \u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default. sectionApi # \u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget. selectedTable # \u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets). viewApi # \u2022 Const viewApi : GristView Interface for the records backing a custom widget. widgetApi # \u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget. Functions # allowSelectBy # \u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message. Returns # Promise < void > clearOptions # \u25b8 clearOptions (): Promise < void > Clears all the options. Returns # Promise < void > fetchSelectedRecord # \u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default. Parameters # Name Type rowId number options FetchSelectedOptions Returns # Promise < any > fetchSelectedTable # \u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default. Parameters # Name Type options FetchSelectedOptions Returns # Promise < any > getAccessToken # \u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); }); Parameters # Name Type options? AccessTokenOptions Returns # Promise < AccessTokenResult > getOption # \u25b8 getOption ( key ): Promise < any > Get single value from Widget options object. Parameters # Name Type key string Returns # Promise < any > getOptions # \u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object. Returns # Promise < null | object > getTable # \u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted. Parameters # Name Type tableId? string Returns # TableOperations mapColumnNames # \u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean Returns # any mapColumnNamesBack # \u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically. Parameters # Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap Returns # any on # \u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101 Parameters # Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function Returns # Rpc onNewRecord # \u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected. Parameters # Name Type callback ( mappings : null | WidgetColumnMap ) => unknown Returns # void onOptions # \u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it. Parameters # Name Type callback ( options : any , settings : InteractionOptions ) => unknown Returns # void onRecord # \u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false . Parameters # Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void onRecords # \u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false . Parameters # Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions Returns # void ready # \u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called. Parameters # Name Type settings? ReadyPayload Returns # void setCursorPos # \u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking. Parameters # Name Type pos CursorPos Returns # Promise < void > setOption # \u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary). Parameters # Name Type key string value any Returns # Promise < void > setOptions # \u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget. Parameters # Name Type options Object Returns # Promise < void > setSelectedRows # \u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget. Parameters # Name Type rowIds null | number [] Returns # Promise < void >","title":"grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#module-grist-plugin-api","text":"","title":"Module: grist-plugin-api"},{"location":"code/modules/grist_plugin_api/#table-of-contents","text":"","title":"Table of contents"},{"location":"code/modules/grist_plugin_api/#interfaces","text":"AccessTokenOptions AccessTokenResult ColumnToMap CursorPos CustomSectionAPI FetchSelectedOptions GristColumn GristDocAPI GristTable GristView InteractionOptions InteractionOptionsRequest ParseOptionSchema ParseOptions ReadyPayload RenderOptions WidgetAPI WidgetColumnMap","title":"Interfaces"},{"location":"code/modules/grist_plugin_api/#type-aliases","text":"ColumnsToMap UIRowId","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#variables","text":"checkers docApi sectionApi selectedTable viewApi widgetApi","title":"Variables"},{"location":"code/modules/grist_plugin_api/#functions","text":"allowSelectBy clearOptions fetchSelectedRecord fetchSelectedTable getAccessToken getOption getOptions getTable mapColumnNames mapColumnNamesBack on onNewRecord onOptions onRecord onRecords ready setCursorPos setOption setOptions setSelectedRows","title":"Functions"},{"location":"code/modules/grist_plugin_api/#type-aliases_1","text":"","title":"Type Aliases"},{"location":"code/modules/grist_plugin_api/#columnstomap","text":"\u01ac ColumnsToMap : ( string | ColumnToMap )[] Tells Grist what columns a Custom Widget expects and allows users to map between existing column names and those requested by the Custom Widget.","title":"ColumnsToMap"},{"location":"code/modules/grist_plugin_api/#uirowid","text":"\u01ac UIRowId : number | \"new\" Represents the id of a row in a table. The value of the id column. Might be a number or \u2018new\u2019 value for a new row.","title":"UIRowId"},{"location":"code/modules/grist_plugin_api/#variables_1","text":"","title":"Variables"},{"location":"code/modules/grist_plugin_api/#checkers","text":"\u2022 Const checkers : Pick < ICheckerSuite , \"CustomSectionAPI\" | \"ParseOptions\" | \"ParseFileResult\" | \"FileSource\" | \"ParseOptionSchema\" | \"GristTables\" | \"EditOptionsAPI\" | \"ParseFileAPI\" | \"RenderTarget\" | \"RenderOptions\" | \"ComponentKind\" | \"GristAPI\" | \"GristDocAPI\" | \"GristView\" | \"GristColumn\" | \"GristTable\" | \"ImportSourceAPI\" | \"ImportProcessorAPI\" | \"ImportSource\" | \"FileContent\" | \"FileListItem\" | \"URL\" | \"InternalImportSourceAPI\" | \"Storage\" | \"WidgetAPI\" > We also create and export a global checker object that includes all of the types above.","title":"checkers"},{"location":"code/modules/grist_plugin_api/#docapi","text":"\u2022 Const docApi : GristDocAPI & GristView A collection of methods for fetching document data. The fetchSelectedTable and fetchSelectedRecord methods are overridden to decode data by default.","title":"docApi"},{"location":"code/modules/grist_plugin_api/#sectionapi","text":"\u2022 Const sectionApi : CustomSectionAPI Interface for the mapping of a custom widget.","title":"sectionApi"},{"location":"code/modules/grist_plugin_api/#selectedtable","text":"\u2022 Const selectedTable : TableOperations Get the current selected table (for custom widgets).","title":"selectedTable"},{"location":"code/modules/grist_plugin_api/#viewapi","text":"\u2022 Const viewApi : GristView Interface for the records backing a custom widget.","title":"viewApi"},{"location":"code/modules/grist_plugin_api/#widgetapi","text":"\u2022 Const widgetApi : WidgetAPI Interface for the state of a custom widget.","title":"widgetApi"},{"location":"code/modules/grist_plugin_api/#functions_1","text":"","title":"Functions"},{"location":"code/modules/grist_plugin_api/#allowselectby","text":"\u25b8 allowSelectBy (): Promise < void > Deprecated now. It was used for filtering selected table by setSelectedRows method. Now the preferred way it to use ready message.","title":"allowSelectBy"},{"location":"code/modules/grist_plugin_api/#returns","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#clearoptions","text":"\u25b8 clearOptions (): Promise < void > Clears all the options.","title":"clearOptions"},{"location":"code/modules/grist_plugin_api/#returns_1","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedrecord","text":"\u25b8 fetchSelectedRecord ( rowId , options? ): Promise < any > Same as GristView.fetchSelectedRecord , but the option keepEncoded is false by default.","title":"fetchSelectedRecord"},{"location":"code/modules/grist_plugin_api/#parameters","text":"Name Type rowId number options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_2","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#fetchselectedtable","text":"\u25b8 fetchSelectedTable ( options? ): Promise < any > Same as GristView.fetchSelectedTable , but the option keepEncoded is false by default.","title":"fetchSelectedTable"},{"location":"code/modules/grist_plugin_api/#parameters_1","text":"Name Type options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_3","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getaccesstoken","text":"\u25b8 getAccessToken ( options? ): Promise < AccessTokenResult > Get an access token, for making API calls outside of the custom widget API. There is no caching of tokens. The returned token can be used to authorize regular REST API calls that access the content of the document. For example, in a custom widget for a table with a Photos column containing attachments, the following code will update the src of an image with id the_image to show the attachment: grist.onRecord(async (record) => { const tokenInfo = await grist.docApi.getAccessToken({readOnly: true}); const img = document.getElementById('the_image'); const id = record.Photos[0]; // get an id of an attachment - there could be several // in a cell, for this example we just take the first. const src = `${tokenInfo.baseUrl}/attachments/${id}/download?auth=${tokenInfo.token}`; img.setAttribute('src', src); });","title":"getAccessToken"},{"location":"code/modules/grist_plugin_api/#parameters_2","text":"Name Type options? AccessTokenOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_4","text":"Promise < AccessTokenResult >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoption","text":"\u25b8 getOption ( key ): Promise < any > Get single value from Widget options object.","title":"getOption"},{"location":"code/modules/grist_plugin_api/#parameters_3","text":"Name Type key string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_5","text":"Promise < any >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#getoptions","text":"\u25b8 getOptions (): Promise < null | object > Gets all options stored by the widget. Options are stored as plain JSON object.","title":"getOptions"},{"location":"code/modules/grist_plugin_api/#returns_6","text":"Promise < null | object >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#gettable","text":"\u25b8 getTable ( tableId? ): TableOperations Get access to a table in the document. If no tableId specified, this will use the current selected table (for custom widgets). If a table does not exist, there will be no error until an operation on the table is attempted.","title":"getTable"},{"location":"code/modules/grist_plugin_api/#parameters_4","text":"Name Type tableId? string","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_7","text":"TableOperations","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnames","text":"\u25b8 mapColumnNames ( data , options? ): any Renames columns in the result using columns mapping configuration passed in ready method. Returns null if not all required columns were mapped or not widget doesn\u2019t support custom column mapping.","title":"mapColumnNames"},{"location":"code/modules/grist_plugin_api/#parameters_5","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap options.reverse? boolean","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_8","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#mapcolumnnamesback","text":"\u25b8 mapColumnNamesBack ( data , options? ): any Offer a convenient way to map data with renamed columns back into the form used in the original table. This is useful for making edits to the original table in a widget with column mappings. As for mapColumnNames(), we don\u2019t attempt to do these transformations automatically.","title":"mapColumnNamesBack"},{"location":"code/modules/grist_plugin_api/#parameters_6","text":"Name Type data any options? Object options.columns? ColumnsToMap options.mappings? null | WidgetColumnMap","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_9","text":"any","title":"Returns"},{"location":"code/modules/grist_plugin_api/#on","text":"\u25b8 on ( eventName , listener ): Rpc Adds the listener function to the end of the listeners array for the event named eventName . No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times. server.on('connection', (stream) => { console.log('someone connected!'); }); Returns a reference to the EventEmitter , so that calls can be chained. By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array. const myEE = new EventEmitter(); myEE.on('foo', () => console.log('a')); myEE.prependListener('foo', () => console.log('b')); myEE.emit('foo'); // Prints: // b // a Since v0.1.101","title":"on"},{"location":"code/modules/grist_plugin_api/#parameters_7","text":"Name Type Description eventName string | symbol The name of the event. listener (\u2026 args : any []) => void The callback function","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_10","text":"Rpc","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onnewrecord","text":"\u25b8 onNewRecord ( callback ): void For custom widgets, add a handler that will be called whenever the new (blank) row is selected.","title":"onNewRecord"},{"location":"code/modules/grist_plugin_api/#parameters_8","text":"Name Type callback ( mappings : null | WidgetColumnMap ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_11","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onoptions","text":"\u25b8 onOptions ( callback ): void For custom widgets, add a handler that will be called whenever the widget options change (and on initial ready message). Handler will be called with an object containing saved json options, or null if no options were saved. The second parameter has information about the widgets relationship with the document that contains it.","title":"onOptions"},{"location":"code/modules/grist_plugin_api/#parameters_9","text":"Name Type callback ( options : any , settings : InteractionOptions ) => unknown","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_12","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecord","text":"\u25b8 onRecord ( callback , options? ): void For custom widgets, add a handler that will be called whenever the row with the cursor changes - either by switching to a different row, or by some value within the row potentially changing. Handler may in the future be called with null if the cursor moves away from any row. By default, options.keepEncoded is false .","title":"onRecord"},{"location":"code/modules/grist_plugin_api/#parameters_10","text":"Name Type callback ( data : null | RowRecord , mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_13","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#onrecords","text":"\u25b8 onRecords ( callback , options? ): void For custom widgets, add a handler that will be called whenever the selected records change. By default, options.format is 'rows' and options.keepEncoded is false .","title":"onRecords"},{"location":"code/modules/grist_plugin_api/#parameters_11","text":"Name Type callback ( data : RowRecord [], mappings : null | WidgetColumnMap ) => unknown options FetchSelectedOptions","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_14","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#ready","text":"\u25b8 ready ( settings? ): void Declare that a component is prepared to receive messages from the outside world. Grist will not attempt to communicate with it until this method is called.","title":"ready"},{"location":"code/modules/grist_plugin_api/#parameters_12","text":"Name Type settings? ReadyPayload","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_15","text":"void","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setcursorpos","text":"\u25b8 setCursorPos ( pos ): Promise < void > Sets the cursor position to a specific row and field. sectionId is ignored. Used for widget linking.","title":"setCursorPos"},{"location":"code/modules/grist_plugin_api/#parameters_13","text":"Name Type pos CursorPos","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_16","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoption","text":"\u25b8 setOption ( key , value ): Promise < void > Store single value in the Widget options object (and create it if necessary).","title":"setOption"},{"location":"code/modules/grist_plugin_api/#parameters_14","text":"Name Type key string value any","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_17","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setoptions","text":"\u25b8 setOptions ( options ): Promise < void > Replaces all options stored by the widget.","title":"setOptions"},{"location":"code/modules/grist_plugin_api/#parameters_15","text":"Name Type options Object","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_18","text":"Promise < void >","title":"Returns"},{"location":"code/modules/grist_plugin_api/#setselectedrows","text":"\u25b8 setSelectedRows ( rowIds ): Promise < void > Set the list of selected rows to be used against any linked widget.","title":"setSelectedRows"},{"location":"code/modules/grist_plugin_api/#parameters_16","text":"Name Type rowIds null | number []","title":"Parameters"},{"location":"code/modules/grist_plugin_api/#returns_19","text":"Promise < void >","title":"Returns"},{"location":"self-managed/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Self-Managed Grist # Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability? The essentials # What is Self-Managed Grist? # There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support. How do I install Grist? # The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups. Grist on AWS # You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page . How do I sandbox documents? # Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem. XSAVE not available # Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\" PTRACE not available # The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration. How do I run Grist on a server? # We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF . How do I set up a team? # Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need. How do I set up authentication? # Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise. Are there other authentication methods? # If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise. How do I enable Grist Enterprise? # Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist Customization # How do I customize styling? # The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL. How do I list custom widgets? # In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field. How do I set up email notifications? # In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS. How do I add more python packages? # The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start. How do I configure webhooks? # It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation. Operations # What are the hardware requirements for hosting Grist? # For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later). What files does Grist store? # When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation. What is a \u201chome\u201d database? # Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows... What is a state store? # Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ... How do I set up snapshots? # Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage . How do I control telemetry? # By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry. How do I upgrade my installation? # We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you. What if I need high availability? # We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"Self-managed Grist"},{"location":"self-managed/#self-managed-grist","text":"Self-Managed Grist The essentials What is Self-Managed Grist? How do I install Grist? Grist on AWS How do I sandbox documents? XSAVE not available PTRACE not available How do I run Grist on a server? How do I set up a team? How do I set up authentication? Are there other authentication methods? How do I enable Grist Enterprise? Customization How do I customize styling? How do I list custom widgets? How do I set up email notifications? How do I add more python packages? How do I configure webhooks? Operations What are the hardware requirements for hosting Grist? What files does Grist store? What is a \u201chome\u201d database? What is a state store? How do I set up snapshots? How do I control telemetry? How do I upgrade my installation? What if I need high availability?","title":"Self-Managed Grist"},{"location":"self-managed/#the-essentials","text":"","title":"The essentials"},{"location":"self-managed/#what-is-self-managed-grist","text":"There are four flavors of Grist: SaaS (Software as a Service): Grist is available as a hosted service at docs.getgrist.com . No installation needed. Free and paid plans, with usage limits. Desktop App : Grist is available as a desktop application, built with Electron. It is available for download at https://github.com/gristlabs/grist-desktop/releases . This desktop application does not need internet and is not tied to any online account or service. Self-Managed Enterprise : Grist is available as a licensed application installed by enterprises on their own infrastructure with our support and backing. Contains proprietary features developed for enterprises with particular needs. Self-Managed Core : Grist is available as a free application installed by citizen developers on their own infrastructure with community support. Grist documents created with our SaaS and Enterprise offerings can be opened and edited with Core, and vice versa. This establishes Grist documents as a reliable format for archiving and interchange. Self-Managed Grist, be it Enterprise or Core, is installed and configured in much the same way, as described in the following sections. For clarity, the sections are tagged with which flavor they apply to, for example: The full source code for Grist Core is always available at github.com/gristlabs/grist-core and is under an Apache-2.0 license. You may use and redistribute Core freely, under the terms of the free software license. The full source for Grist Enterprise is also available, at github.com/gristlabs/grist-ee , under a proprietary license that does not grant any automatic rights to use or redistribute the software. You can evaluate Enterprise for 30 days using the instructions in the following sections, or sign up for our Grist Enterprise plan and get support.","title":"What is Self-Managed Grist?"},{"location":"self-managed/#how-do-i-install-grist","text":"The easiest way to install Grist is as a container. We will describe how using Docker , but there are many other tools and services for running containers. To try Grist out using Docker, make an empty directory for Grist to store material in (say ~/grist ) and then you can do: docker run -p 8484:8484 \\ -v ~/grist:/persist \\ -e GRIST_SESSION_SECRET=invent-a-secret-here \\ -it gristlabs/grist You should then be able to visit http://localhost:8484 in your browser. Already you will be able to create and edit Grist documents, and to open and edit documents downloaded from another Grist installation (such as our SaaS). If using some other tool or service, here are the important points: The main image name is gristlabs/grist , which is our combined Core and Enterprise docker image. The image gristlabs/grist-oss also exists, which uses only free and open source code. This image uses only Grist Core, and has no enterprise features available. (For some tools such as Podman, you may need to prefix these image names with docker.io/ .) A volume (or mount, or directory) needs to be available at location /persist within the container. It can be initially empty - Grist will populate it. Without this volume, nothing you do will be stored long-term. Port 8484 on the container needs to be exposed. This can be changed if you also set the PORT environment variable for the container. The environment variable GRIST_SESSION_SECRET should be set to something secret for the container. Installed this way, Grist is accessible only to you. Typically you want to take at least the following steps: Set up sandboxing - this is important to place bounds on what formulas can do. Serve from a public host so you can collaborate live with others. Enable an authentication method so users can log in. Often you\u2019ll want to hook Grist up to an \u201cSSO\u201d (Single Sign-On) service you already use. We support some very general authentication methods that cover many cases, and a special authentication method for custom cases. Consider enabling snapshot support if you want Grist to handle document backups.","title":"How do I install Grist?"},{"location":"self-managed/#grist-on-aws","text":"You can also host Grist on AWS. Full instructions on this hosting method are available on the Grist AWS Marketplace page .","title":"Grist on AWS"},{"location":"self-managed/#how-do-i-sandbox-documents","text":"Grist allows for very powerful formulas, using Python. We recommend setting the environment variable GRIST_SANDBOX_FLAVOR to gvisor if your hardware supports it (most will), to run formulas in each document within a sandbox isolated from other documents and isolated from the network. docker run ... -e GRIST_SANDBOX_FLAVOR=gvisor \\ ... To sanity-check that formulas are being evaluated within a sandbox, you can create a document and then check that this formula gives an empty result: import glob glob.glob('/etc/*') Here are some reasons why gvisor sandboxing, as configured for Grist, may fail, and what you can do to diagnose the problem.","title":"How do I sandbox documents?"},{"location":"self-managed/#xsave-not-available","text":"Your processor may not be supported. On x86_64 , Sandy Bridge or later is needed. Check that the XSAVE processor flag is set. Here\u2019s a quick way to test that: grep -q '\\bxsave\\b' /proc/cpuinfo && echo \"XSAVE enabled\" || echo \"XSAVE missing\"","title":"XSAVE not available"},{"location":"self-managed/#ptrace-not-available","text":"The SYS_PTRACE capability may not be available. If running in docker, you could try explicitly granting it, if you are comfortable with making it available: docker run ... --cap-add=SYS_PTRACE ... In some cloud environments such as AWS ECS, you may need to explicitly list this capability in your container configuration.","title":"PTRACE not available"},{"location":"self-managed/#how-do-i-run-grist-on-a-server","text":"We suggest that you become familiar with all the other aspects of self-management on this page before serving Grist from a public host (especially Sandboxing ). When you do, it is important to tell Grist where it will be served from, using the APP_HOME_URL variable. For example, if you will be serving from https://grist.example.com , let Grist know like this: docker run ... -e APP_HOME_URL=\"https://grist.example.com\" \\ ... You will need to place a \u201creverse proxy\u201d in front of Grist to handle \u201cSSL termination\u201d (decrypting encypted traffic) using a certificate that establishes ownership of the site. If you don\u2019t know what this means, you could try using the Grist Omnibus which packages Grist with a reverse proxy that will use Let\u2019s Encrypt to get a certificate for you automatically. An important job of such a proxy is to correctly forward websocket connections. This amounts to two requirements: Ensure that the proxy is using HTTP 1.1 Pass the necessary Upgrade, Connection, and Host HTTP headers so that an HTTP connection can be upgraded to a websocket connection. For example, here is a minimal configuration for nginx , a possible choice for reverse proxy. server { server_name grist.example.com; location / { proxy_pass http://localhost:8484; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } } This configuration will handle basic HTTP traffic and websockets. It still requires additional SSL/TLS configuration. A simple option for self-hosting on a small scale is to use certbot by the EFF .","title":"How do I run Grist on a server?"},{"location":"self-managed/#how-do-i-set-up-a-team","text":"Grist has a concept of \u201cteam sites\u201d that are independently managed and named areas containing their own workspaces and documents. Team sites can have distinct subdomains (as on our SaaS\u2019s hosted team sites ), or be distinguished by a special path prefix. This often does not make sense for self-managed installations, where there is a single team. With a single domain and a single team, the special path prefix (which looks like /o/ ) is an inelegant waste of space in URLs. So you can direct Grist to use a single team by setting GRIST_SINGLE_ORG (\u201corg\u201d or \u201corganization\u201d is a synonym for team): docker run ... -e GRIST_SINGLE_ORG=cool-beans The name of the team should use only the lower-case characters a-z, the digits 0-9, and the hyphen ( - ). You may also want to look into Custom styling to hide any UI elements you don\u2019t need.","title":"How do I set up a team?"},{"location":"self-managed/#how-do-i-set-up-authentication","text":"Authentication can be set up in many ways for Grist Core and Enterprise, using SAML, OpenID Connect or forwarded headers. Between the two, many popular SSOs can be hooked up, such as Google or Microsoft sign-ins. SAML . OpenID Connect Forwarded headers . For any authentication method, you may want to also consider setting the following variables: COOKIE_MAX_AGE : (optional) expiration date for Grist session cookie, when set to none session cookie will be in a Session mode - it should be removed after closing a browser. If set to a number, the units of the number are milliseconds. GRIST_FORCE_LOGIN : (optional) when set to true this will instruct Grist to redirect anonymous users to a login page. For our SaaS, we use a custom authentication system based around AWS Cognito. Currently, we have no plans to release that as part of Core or Enterprise.","title":"How do I set up authentication?"},{"location":"self-managed/#are-there-other-authentication-methods","text":"If users on your site login via WordPress, or via a custom mechanism you developed, you may want to consider GristConnect , available for Grist Enterprise.","title":"Are there other authentication methods?"},{"location":"self-managed/#how-do-i-enable-grist-enterprise","text":"Grist Enterprise can be enabled by visiting the Admin Panel and clicking the \u2018Enable Grist Enterprise Features\u2019 toggle. This will cause Grist to automatically restart. You should now have an unactivated version of Grist Enterprise, with a 30 day trial period. Activation keys are used to run Grist Enterprise after a trial period of 30 days has expired. Get an activation key by signing up for Grist Enterprise . You don\u2019t need an activation key to run Grist Core, and can revert back to Core at any time using the toggle in the Admin Panel. Place the contents of your activation key in an environment variable called GRIST_ACTIVATION , or place it in a directory available to Grist and provide the full path to the file with the environment variable GRIST_ACTIVATION_FILE . Without the activation key, there will be a banner stating that Grist is in trial mode. Once the activation key is detected, this banner will go away. Replacing the activation key will require restarting Grist. docker run ... -e GRIST_ACTIVATION= \\ -it gristlabs/grist","title":"How do I enable Grist Enterprise?"},{"location":"self-managed/#customization","text":"","title":"Customization"},{"location":"self-managed/#how-do-i-customize-styling","text":"The Grist UI has many elements, some of which may not be relevant to you. For self-managed installations of Grist, you can turn off many elements using GRIST_HIDE_UI_ELEMENTS . This is comma-separated list of parts of the UI to hide. The allowed names of parts are: helpCenter,billing,templates,multiSite,multiAccounts . The UI elements present are also affected by whether GRIST_SINGLE_ORG is set. docker run ... -e GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,multiSite,multiAccounts \\ ... By default pages of the Grist UI have - Grist added to their title. You can change this by setting GRIST_PAGE_TITLE_SUFFIX : docker run ... -e GRIST_PAGE_TITLE_SUFFIX=\" - Cool Beans\" \\ ... You can set the suffix to \"_blank\" to entirely remove it. You can also override the CSS styling of the site if you set APP_STATIC_INCLUDE_CUSTOM_CSS to true . docker run ... -e APP_STATIC_INCLUDE_CUSTOM_CSS=true \\ ... This will load an extra custom.css file. You can find an example of such a file in the Grist Core repository . The file includes most of our global CSS variables for colors, and a few variables for the logo shown in the top-left corner. There\u2019s really no limit to what can go in the file, so specifying arbitrary styles is possible. Note that all CSS rules should use !important to make sure they have the highest precedence. Otherwise, it\u2019s possible for more specific rules included by our framework to take precedence. To override custom.css , you can make your own copy and make sure Grist uses it. If the CSS file is in your current directory, then do: docker run ... -v $PWD/custom.css:/grist/static/custom.css ... It is possible to direct Grist to load static resources from a CDN by setting APP_STATIC_URL . If you do so, and you are using custom CSS, you\u2019ll need to ensure the custom CSS is available from that base URL.","title":"How do I customize styling?"},{"location":"self-managed/#how-do-i-list-custom-widgets","text":"In our SaaS, Grist has a list of pre-built custom widgets available in the UI. You can have your self-managed installation offer the same list by setting the following: docker run ... -e GRIST_WIDGET_LIST_URL=\"https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json\" \\ ... This is optional. If you leave the variable unset, documents with custom widgets will still work fine, but you\u2019ll need to enter a full URL when adding custom widgets, rather than picking a widget from the gallery. You can make your own list of widgets available by forking github.com/gristlabs/grist-widget or by manually preparing a .json file on a public server in the same format as our manifest.json . To customize the appearance of widgets in the gallery, the following fields may be set in the manifest.json : name : The widget title. description : An optional description shown below the widget title. authors : An optional list of one or more widget authors. The first author will be shown in the \u201cDeveloper\u201d field. lastUpdatedAt : The date shown in the \u201cLast updated\u201d field.","title":"How do I list custom widgets?"},{"location":"self-managed/#how-do-i-set-up-email-notifications","text":"In Grist SaaS, we send emails such as invitations to share a document using SendGrid . The same mechanism is available in Grist Enterprise. There is not yet an equivalent in Grist Core. You will need to set a SendGrid API key: docker run ... -e SENDGRID_API_KEY=SG.XXXXXXX.XXXXX \\ ... You will need to make a file config.json available in the root of the volume mapped to /persist . Its contents should be as follows: { \"sendgrid\": { \"api\": { \"prefix\": \"https://api.sendgrid.com/v3\", \"enroll\": \"/marketing/contacts\", \"search\": \"/marketing/contacts/search\", \"searchByEmail\": \"/marketing/contacts/search/emails\", \"listRemove\": \"/marketing/lists/{{id}}/contacts\", \"send\": \"/mail/send\" }, \"address\": { \"from\": { \"email\": \"\", \"name\": \"the name to show with email\" } }, \"template\": { \"invite\": \"d-f9.....\", \"billingManagerInvite\": \"d-f9.....\", \"memberChange\": \"d-b3.....\" }, \"list\": { \"singleUserOnboarding\": \"b22...\" }, \"unsubscribeGroup\": { \"invites\": 19..., \"billingManagers\": 19.... } } } Here are the meanings of the keys in this file: sendgrid.api - Values should remain unchanged from what\u2019s defined in the sample. These control API versioning and endpoints. Grist currently targets v3 of SendGrid\u2019s web API. sendgrid.address - Should be set to a verified email address and name of a SendGrid sender. This controls the \u201cFrom\u201d address of all emails sent via SendGrid (e.g. invites sent on behalf of Grist users). sendgrid.template - Maps Grist actions to SendGrid email templates ids. These are for transactional emails that are sent as a result of some action occurring in Grist. sendgrid.template.invite - This is for emails sent to users that are invited to documents, workspaces, or sites. sendgrid.template.memberChange - This is for emails sent to billing managers when users are added/removed from sites. sendgrid.list - Maps Grist actions to SendGrid marketing list ids. These are for on-going automated emails that are sent to all users who are enrolled in a particular list. sendgrid.list.singleUserOnboarding - New Grist users are automatically added to this list on first-login. This is suitable for sending regular onboarding emails to users. sendgrid.unsubscribeGroup - Maps email types to SendGrid unsubscribe group ids. These are for allowing users to unsubscribe from receiving certain types of emails (via the link in the email). sendgrid.unsubscribeGrist.invites - If set, invite emails can be suppressed via the unsubscribe link in emails. sendgrid.unsubscribeGrist.billingManagers - If set, emails sent specifically to billing managers (e.g. membership changes) can be suppressed via the unsubscribe link in emails. For reference, there are example SendGrid templates in example-sendgrid-templates.zip based on an export of the SendGrid templates for our SaaS.","title":"How do I set up email notifications?"},{"location":"self-managed/#how-do-i-add-more-python-packages","text":"The set of python packages available for use in formulas is currently not configurable. You can add packages anyway if you are willing to build and install your own version of the Grist. Warning Grist documents made on an installation with custom python packages will not bring those packages with them if copied to a different installation. Formulas using custom python packages will give errors when those packages are unavailable. Create an empty directory, and add the following into it, in a file called Dockerfile : FROM gristlabs/grist # or grist-oss or grist-omnibus RUN \\ apt update && apt install -y openssl && \\ python3 -m pip install phonenumbers Replace phonenumbers with the python package or packages you want to install. You can now build your custom Grist image by running a docker build in the directory with Dockerfile in it: # replace \"custom\" with a username or organization name. docker build -t custom/grist . Once done, you can use custom/grist in place of gristlabs/grist(-ee) in How do I install Grist , and your python library will now be available to import in formalas. If you want the import done automatically, so you don\u2019t have to do it in formulas, currently that requires a code change to sandbox/grist/gencode.py . If you are comfortable making code changes, then the build instructions of the grist-core repository are the place to start.","title":"How do I add more python packages?"},{"location":"self-managed/#how-do-i-configure-webhooks","text":"It is possible to use webhooks to enable integrations with external services. The webhooks documentation has further details. Note that when self-hosting, only external services allowed by the ALLOWED_WEBHOOK_DOMAINS environment variable are allowed. There are security concerns with allowing any domain, as internal Grist services may become vulnerable to manipulation.","title":"How do I configure webhooks?"},{"location":"self-managed/#operations","text":"","title":"Operations"},{"location":"self-managed/#what-are-the-hardware-requirements-for-hosting-grist","text":"For hosting Grist as a Linux container, here is a known good configuration for a variety of moderate workloads: 8GB RAM 2 CPUs 20GB disk Grist is packaged for the following CPU architectures: x86_64 (Sandy Bridge or later if sandboxing is enabled) ARM64 Every Grist document is a separate database, so it is difficult to state absolute minimum requirements without knowing what documents will be used. In tests, the Investment Research template runs comfortably served from a Grist container with: 100MB RAM without sandboxing enabled. 200MB RAM with sandboxing enabled. 1 CPU. Memory and CPU requirements will scale with the number of documents simultaneously in use by your team. Sandboxing is an important issue in serving Grist. It is achieved using gvisor . Sandboxing depends on the availability of particular capabilities, and may be unavailable in environments that deny or lack these capabilities. Grist sandboxing is known to work in the following environments: Regular unprivileged docker containers with default security settings. AWS EC2 instances. AWS Fargate containers, with SYS_PTRACE set in linuxParameters.capabilities . Grist sandboxing has been reported to fail to initialize on older Intel processors that do not support the XSAVE feature (supported by Sandy Bridge and later).","title":"What are the hardware requirements for hosting Grist?"},{"location":"self-managed/#what-files-does-grist-store","text":"When installed as a container, Grist expects to have access to a persistent volume, or a directory shared with the host, in which it stores everything that needs to last beyond a container restart. Concretely, if you started Grist exactly as described in How do I install Grist , that directory would be ~/grist . Here\u2019s what you would find there: A subdirectory called docs , containing *.grist files. These are Grist documents. Grist documents are SQLite databases, so you can inspect these files with the standard sqlite3 utility. You can also upload them to another installation of Grist (such as our hosted service) and view/edit them there. If you move or rename these files, Grist will no longer recognize them. If snapshot support is configured, there will be extra files alongside each .grist file for tracking its storage state. A file called grist-sessions.db . This contains information to support browser sessions with Grist. It is a SQLite database. If redis is configured , that is used instead of this file. A file called home.sqlite3 . This contains information about teams, workspaces, and documents (metadata only, such as names, rather than document contents such as tables and cells). It is a SQLite database. It is called the home database and if PostgreSQL is configured that is used instead of this file. If using Grist Omnibus, there are other files, including: An auth directory, with a SQLite database for tracking login state, and a store of any certificates created. A param directory, with secrets invented for the installation.","title":"What files does Grist store?"},{"location":"self-managed/#what-is-a-home-database","text":"Grist stores metadata about users, documents, workspaces, etc in a database called the \u201chome\u201d database. This does not contain the material inside documents such as tables and columns, but does contain document names and creation times, for example. By default, Grist will create a home database in an Sqlite file within the /persist directory. To use instead a PostgreSQL database, create the database along with a user with sufficient access to create tables, and set the following variables: TYPEORM_TYPE - set to postgres TYPEORM_DATABASE - set to name of database, e.g. home TYPEORM_USERNAME - set to postgres username with rights to the database TYPEORM_PASSWORD - set to postgres password with rights to the database TYPEORM_HOST - set to hostname of database, e.g. grist.mumble.rds.amazonaws.com TYPEORM_PORT - set to port number of database if not the default for PostgreSQL Grist is known to work with PostgreSQL from versions 10 through 16. Recent versions, however, have enabled by default a JIT compiler that is known to cause problems with Grist, which expresses itself as every cell operation taking a few noticeable seconds. In case this happens, PostgreSQL\u2019s JIT compiler should be disabled for Grist with the command-line argument -c jit=off or via other methods of changing the PostgreSQL configuration . In a docker-compose.yaml file, for example, the JIT compiler can be disabled like this: postgres: image: postgres:latest command: -c jit=off # other config follows...","title":"What is a “home” database?"},{"location":"self-managed/#what-is-a-state-store","text":"Grist can be configured to use Redis as an external state cache. For most Grist functionality, this is optional. It is required for webhook support, and recommended for snapshot support. To use, just set REDIS_URL to something like redis://hostname/N where N is a redis database number. docker run ... -e REDIS_URL=\"redis://hostname/N\" ...","title":"What is a state store?"},{"location":"self-managed/#how-do-i-set-up-snapshots","text":"Grist\u2019s cloud storage feature allows automatic syncing of Grist documents and document versions to an S3-compatible bucket (available for all Grist versions) or to Azure storage (in Enterprise Grist). Here is an example of running Grist locally, with snapshots stored in a test MinIO instance: # Make a network docker network create grist # Start Redis in our network (recommended for snapshots) docker run --rm --network grist --name redis redis # Start MinIO in our network docker run --rm --network grist --name minio \\ -v /tmp/minio:/data \\ -p 9000:9000 -p 9001:9001 \\ -e MINIO_ROOT_USER=grist -e MINIO_ROOT_PASSWORD=admingrist \\ -it minio/minio server /data -console-address \":9001\" # Visit http://localhost:9000 and set up a bucket called grist-docs. # Make sure to enable versioning on the bucket. # Hook Grist up to Redis and MinIO docker run --rm --network grist \\ -e GRIST_DOCS_MINIO_ACCESS_KEY=grist \\ -e GRIST_DOCS_MINIO_SECRET_KEY=admingrist \\ -e GRIST_DOCS_MINIO_USE_SSL=0 \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ -e GRIST_DOCS_MINIO_ENDPOINT=minio \\ -e GRIST_DOCS_MINIO_PORT=9000 \\ -e REDIS_URL=redis://redis \\ -v /tmp/grist:/persist -p 8484:8484 -it gristlabs/grist Here are flags to make Grist talk to an AWS S3 bucket using the MinIO client: ... -e GRIST_DOCS_MINIO_ACCESS_KEY=$AWS_ACCESS_KEY_ID \\ -e GRIST_DOCS_MINIO_SECRET_KEY=$AWS_SECRET_ACCESS_KEY \\ -e GRIST_DOCS_MINIO_ENDPOINT=s3.amazonaws.com \\ -e GRIST_DOCS_MINIO_BUCKET=grist-docs \\ ... As per MinIO specs .), the default bucket region is us-east-1 . This default region can be overwritten using the GRIST_DOCS_MINIO_BUCKET_REGION flag. For details, and other options, see Cloud Storage .","title":"How do I set up snapshots?"},{"location":"self-managed/#how-do-i-control-telemetry","text":"By default, Grist installations do not \u201cphone home\u201d to any central service. It is useful to permit them to do so, to give Grist Labs some limited insight into your usage, through measurements called telemetry. This will help guide development, and draw attention to self-managed users as a group. The simplest way for an owner of a Grist installation to opt in to sending telemetry to Grist Labs is to click the \u201cOpt in to Telemetry\u201d button on the \u201cSupport Grist\u201d banner on the main page of the installation. If you do not wish to opt in, you can dismiss the banner. The banner is shown only to the owner of the installation. The owner of the installation is the user whose email address matches the GRIST_DEFAULT_EMAIL environment variable (if set). You can control telemetry at any time using the \u201cSupport Grist\u201d page, if you are the owner of the installation. Rather than using buttons to opt in to telemetry, you may set the environment variable GRIST_TELEMETRY_LEVEL to limited . This has the same effect as the \u201cOpt in to Telemetry\u201d button. The GRIST_TELEMETRY_LEVEL environment variable, if set, takes priority over any setting made interactively. Recommended values are limited or off . In either case, read limited telemetry for exact details of what data is sent, and telemetry overview for further explanation. An interactive method for controlling telemetry is only available for Grist Core builds currently. In all cases, the default is to not send telemetry.","title":"How do I control telemetry?"},{"location":"self-managed/#how-do-i-upgrade-my-installation","text":"We currently release new Grist Core and Enterprise images at approximately weekly intervals. Grist handles any migrations that may be needed to the documents or databases it uses. Utilities such as Watchtower can keep your version of Grist up to date for you.","title":"How do I upgrade my installation?"},{"location":"self-managed/#what-if-i-need-high-availability","text":"We have developed expertise in hosting very busy Grist installations, with many users, including how to upgrade with minimal disruption, and how to scale out to handle heavy load. We would be happy to help Enterprise clients with needs of this nature.","title":"What if I need high availability?"},{"location":"install/saml/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . SAML # Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy. Example: Auth0 # For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert . Example: Authentik # In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/ Example: Google # In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose. Troubleshooting # We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"SAML"},{"location":"install/saml/#saml","text":"Configuration for SAML, useful for enterprise single-sign-on logins. A good informative overview of SAML is at https://www.okta.com/integrate/documentation/saml/ Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Okta or Google Apps. We will need one or more certificates from the IdP, in PEM format. This is a public key that Grist will use to check messages from the IdP are legit. We will need a private and public key pair for Grist to use when communicating with the IdP. The IdP will need to know the public key for Grist, to check messages from Grist are legit. Expected environment variables: GRIST_SAML_SP_HOST - this is just the base URL of the Grist site, such as https:// (when SAML is active, there will be a /saml/assert endpoint available here for implementing the protocol). GRIST_SAML_SP_KEY - path to a file with our private key, in PEM format. This is the private key of the key pair created for Grist to use with the IdP. GRIST_SAML_SP_CERT - path to file with our public key, in PEM format. This is the public key of the key pair created for Grist to use with the IdP. It is not the public key/certificate of the IdP. GRIST_SAML_IDP_LOGIN - login url to redirect user to for log-in. GRIST_SAML_IDP_LOGOUT - logout URL to redirect user to for log-out. GRIST_SAML_IDP_SKIP_SLO - if set and non-empty, don\u2019t attempt \u201cSingle Logout\u201d SAML flow, but simply redirect to GRIST_SAML_IDP_LOGOUT after clearing session. Whether this flow is possible will depend on the IdP. GRIST_SAML_IDP_CERTS - comma-separated list of paths for certificates from the IdP, in PEM format. This is not the private or public key created for Grist. GRIST_SAML_IDP_UNENCRYPTED - if set and non-empty, allow unencrypted assertions, relying on https for privacy.","title":"SAML"},{"location":"install/saml/#example-auth0","text":"For example, when running on localhost and http, settings that work with the Auth0 SAML IdP are: GRIST_SAML_IDP_SKIP_SLO not set GRIST_SAML_SP_HOST = http://localhost:8484 GRIST_SAML_IDP_UNENCRYPTED = 1 GRIST_SAML_IDP_LOGIN = https://...auth0.com/samlp/xxxx GRIST_SAML_IDP_LOGOUT = https://...auth0.com/samlp/xxxx (these are same for Auth0) GRIST_SAML_IDP_CERTS = .../auth0.pem (downloaded per Auth0 instructions) GRIST_SAML_SP_KEY = .../saml.pem (created) GRIST_SAML_SP_CERT = .../saml.crt (created) When used with docker, make sure that the key and certificate files are accessible within a shared volume. The key/cert pair were created following instructions here: Auth0: use custom certificate to sign requests Auth0 as the SAML identity provider In your Auth0 settings also make sure that: The \u201cApplication Callback URL\u201d is set to https:///saml/assert .","title":"Example: Auth0"},{"location":"install/saml/#example-authentik","text":"In Authentik , add a Provider called Grist with: ACS URL: https:///saml/assert Set Service provider binding to Post Select or add a signing certificate. You\u2019ll need to download this to use as GRIST_SAML_IDP_CERTS in Grist configuration. Add a verification certificate. This will be the public part of a key pair your create for GRIST_SAML_SP_KEY / GRIST_SAML_SP_CERT in Grist configuration. Then, still in Authentik, add an Application also called Grist (I\u2019m not very imaginative) that: Uses the Grist Provider. Has Launch URL set to https:// . The Grist settings follow the same pattern as for Auth0. The login and logout URLs with Authentik at the time of writing look like: GRIST_SAML_IDP_LOGIN = https://...authentik.../application/saml/grist/sso/binding/redirect/ GRIST_SAML_IDP_LOGOUT = https://...authentik.../if/session-end/grist/","title":"Example: Authentik"},{"location":"install/saml/#example-google","text":"In Google Admin , under the \u201cApps\u201d section, in \u201cWeb and Mobile Apps\u201d, add a new custom SAML app. Set the app name, description and icon to your liking, and click on Next. Take note of the contents of the field SSO URL , and download the provided certificate. Configure the app in Google Admin as follows: ACS URL: https:///saml/assert Entity ID: https:///saml/metadata.xml Check the Signed Response checkbox. Leave Start URL empty, and the Name ID settings as default. Click on Next, and optionally, under Attributes, add two mappings for automatic name population: Google Directory attribute First Name set to App attribute FirstName Google Directory attribute Last Name set to App attribute LastName Then click on Finish, and configure Grist\u2019s settings: GRIST_SAML_IDP_SKIP_SLO = true GRIST_SAML_SP_HOST = https:// GRIST_SAML_IDP_UNENCRYPTED = true GRIST_SAML_IDP_LOGIN = https://accounts.google.com/o/saml2/idp?idpid=xxxx (provided to you by Google as SSO URL ) GRIST_SAML_IDP_LOGOUT = https:// (since Google does not support Single Logout, just return the user to the homepage) GRIST_SAML_IDP_CERTS = .../google.pem (provided to you by Google earlier) GRIST_SAML_SP_KEY = .../saml.pem GRIST_SAML_SP_CERT = .../saml.crt To create the keypair used in GRIST_SAML_SP_KEY and GRIST_SAML_SP_CERT , follow the same instructions as for Auth0 and Authentik. Note: Google does not verify incoming SAML messages, so they do not allow uploading a public key for that purpose.","title":"Example: Google"},{"location":"install/saml/#troubleshooting","text":"We expect IdP to provide us with name_id, a unique identifier for the user. We also use optional attributes for the user\u2019s name, for which we accept any of: FirstName LastName http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname You may need to tweak your IdP\u2019s defaults to match Grist\u2019s expectations.","title":"Troubleshooting"},{"location":"install/oidc/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . OpenID Connect # Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options Example: Gitlab # See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Auth0 # Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" . Example: Keycloak # First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"OIDC"},{"location":"install/oidc/#openid-connect","text":"Configuration for OIDC, useful for organization single-sign-on logins. A good informative overview of OIDC is at https://developer.okta.com/blog/2019/10/21/illustrated-guide-to-oauth-and-oidc Note: SP is \u201cService Provider\u201d, in our case, the Grist application. IdP is the \u201cIdentity Provider\u201d, somewhere users log into, e.g. Keycloak, Authelia, \u2026 OIDC is the acronym for OpenID Connect Expected environment variables: GRIST_OIDC_IDP_ISSUER - the issuer URL for the IdP, passed to node-openid-client, see: https://github.com/panva/node-openid-client/blob/a84d022f195f82ca1c97f8f6b2567ebcef8738c3/docs/README.md#issuerdiscoverissuer . This variable turns on the OIDC login system. GRIST_OIDC_IDP_CLIENT_ID - the client ID for the application, as registered with the IdP. GRIST_OIDC_IDP_CLIENT_SECRET - the client secret for the application, as registered with the IdP. GRIST_OIDC_IDP_SCOPES (optional) - the scopes to request from the IdP, as a space-separated list. Defaults to \"openid email profile\" . GRIST_OIDC_SP_HOST (optional) - this is just the base URL of the Grist site, such as https:// (when OIDC is active, there will be a /oauth2/callback endpoint available here for implementing the protocol). If omitted, APP_HOME_URL will be used. GRIST_OIDC_IDP_END_SESSION_ENDPOINT (optional) - If set, overrides the IdP\u2019s end_session_endpoint with an alternative URL to redirect user upon logout (for an IdP that has a logout endpoint but does not support the OIDC RP-Initiated Logout specification). GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT (optional) - If set to \u201ctrue\u201d, on logout, there won\u2019t be any attempt to call the IdP\u2019s end_session_endpoint (the user will remain logged in in the IdP). You should only set it to \u201ctrue\u201d if the IdP does not provide such endpoint (for example if you use Gitlab). GRIST_OIDC_SP_PROFILE_NAME_ATTR (optional) - The key of the attribute to use for the user\u2019s name. If omitted, the name will be the concatenation of given_name + family_name if they are provided or the name attribute otherwise. GRIST_OIDC_SP_PROFILE_EMAIL_ATTR (optional) - The key of the attribute to use for the user\u2019s email. Defaults to \u201cemail\u201d. GRIST_OIDC_IDP_ENABLED_PROTECTIONS (optional) - A comma-separated list of protections to enable. Supported values are PKCE , STATE , NONCE . Defaults to PKCE,STATE , which is the recommended settings. It\u2019s highly recommended that you enable STATE , and at least one of PKCE or NONCE, depending on what your OIDC provider requires/supports. GRIST_OIDC_IDP_ACR_VALUES (optional) - A space-separated list of ACR values to request from the IdP. GRIST_OIDC_IDP_EXTRA_CLIENT_METADATA (optional) - A JSON object with extra client metadata to pass to openid-client. Be aware that setting this object may override any other values passed to the openid client. More info: https://github.com/panva/node-openid-client/tree/main/docs#new-clientmetadata-jwks-options","title":"OpenID Connect"},{"location":"install/oidc/#example-gitlab","text":"See how to create an OAuth2 application in Gitlab in this documentation . While creating the application, set the redirect URI to https:///oauth2/callback (or http://localhost:8484/oauth2/callback if tested locally, change 8484 to the port you listen on) and select the scopes you will specify in GRIST_OIDC_IDP_SCOPES . Once the application is set up, start Grist with these settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https://gitlab.com/.well-known/openid-configuration GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Gitlab for the application GRIST_OIDC_IDP_CLIENT_SECRET=... # Gitlab doesn't propose `end_session_endpoint` GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Gitlab"},{"location":"install/oidc/#example-auth0","text":"Create an application in Auth0 as explained in this documentation (you can select the app type named Regular Web Applications ). Once the application is created, ensure to add at least the following configuration for the app: Allowed callback URLs: https:///oauth2/callback Allowed logout URLs: http:///* (you can also replace the wildcard with the whole path in order to be stricter) Then you should be able to start Grist with the following settings: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:// GRIST_OIDC_IDP_SCOPES=openid profile email # the client ID generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Auth0 for the application GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Auth0"},{"location":"install/oidc/#example-keycloak","text":"First of all, set up Keycloak as explained in one of these \u201cGetting started\u201d guides: https://www.keycloak.org/guides#getting-started . Once keycloak is set up with a realm and a user, create a new client with the following configuration: Client type: OpenID Connect Authentication flow: Standard Flow Root URL: https:// Valid redirect URIs: /oauth2/callback Valid post logout redirect URIs: /* (you can also replace the wildcard with the whole path in order to be stricter) Submit your settings and go to the Credentials tab to retrieve the client secret. Then, you can start Grist with the following configuration: GRIST_OIDC_SP_HOST=https:// # or http://localhost:8484 GRIST_OIDC_IDP_ISSUER=https:///realms/ GRIST_OIDC_IDP_SCOPES=openid profile email # the ID you chose for the Keycloak client GRIST_OIDC_IDP_CLIENT_ID=... # the client secret generated by Keycloak retrieved earlier GRIST_OIDC_IDP_CLIENT_SECRET=... This format is suitable for an .env file or similar. From a shell invocation, remember to quote values with spaces, such as GRIST_OIDC_IDP_SCOPES=\"openid profile email\" .","title":"Example: Keycloak"},{"location":"install/forwarded-headers/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Forwarded Headers # You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site. Example: traefik-forward-auth # traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus . Troubleshooting # For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Forwarded headers"},{"location":"install/forwarded-headers/#forwarded-headers","text":"You may have a middleware that does authentication and then passes identity on to web applications in a header. If you do, then Grist can be configured to respect that header. Warning The redirection logic for authentication using forwarded headers currently assumes a single team site configuration, and may misbehave for multi-site configurations. To make this work, here is what you\u2019ll need to do: Set GRIST_FORWARD_AUTH_HEADER to a header that will contain authorized user emails, say x-forwarded-user . This needs to match what your middleware will set. Make sure the /auth/login path is handled by your middleware before reaching Grist. Set GRIST_FORWARD_AUTH_LOGOUT_PATH to a path that will trigger a logout for your middleware (for example, /_oauth/logout ). Make sure that the logout path is handled by your middleware! If you want to allow anonymous access in some cases, make sure all other Grist paths are free of your middleware. Grist will trigger the middleware (by redirecting to /auth/login ) as needed. It\u2019s a good idea to strip GRIST_FORWARD_AUTH_HEADER from outside requests on all paths that aren\u2019t handled by your middleware. Your middleware may allow you to specify where to forward the user to after logging out. That should be /signed-out on the Grist site.","title":"Forwarded Headers"},{"location":"install/forwarded-headers/#example-traefik-forward-auth","text":"traefik-forward-auth is \u201cA minimal forward authentication service that provides OAuth/SSO login and authentication for the traefik reverse proxy/load balancer.\u201d The GRIST_FORWARD_AUTH_HEADER should be X-Forwarded-User , and this should be set in the authResponseHeaders settings for traefik. The GRIST_FORWARD_AUTH_LOGOUT_PATH should be /_oauth/logout , unless you have changed the default url-path setting for traefik-forward-auth. LOGOUT_REDIRECT for traefik-forward-auth should be https:///signed-out . There are worked examples at A template for self-hosting Grist with traefik and docker compose and Grist Omnibus .","title":"Example: traefik-forward-auth"},{"location":"install/forwarded-headers/#troubleshooting","text":"For many, this method of authentication works great. A user with multiple web apps served by the same middleware had difficulty coordinating logouts. That could be resolved by applying the middleware to all Grist paths and setting GRIST_IGNORE_SESSION=true so Grist has no separate notion of who is signed in. But then sharing some documents with everyone publically (without signing in) became a problem. Note that with GRIST_IGNORE_SESSION=true , Grist will trust GRIST_FORWARD_AUTH_HEADER on all requests, so it is imperative that you have middleware that overrides or strips this header for all outside requests before forwarding them to Grist. If on the contrary you want to be sure the user must be logged in before using Grist in any way, you can set GRIST_FORCE_LOGIN=true .","title":"Troubleshooting"},{"location":"install/cloud-storage/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Cloud Storage # This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration. S3-compatible stores via MinIO client # Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance. Azure # For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX . S3 with native AWS client # For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables. Usage once configured # Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Cloud storage"},{"location":"install/cloud-storage/#cloud-storage","text":"This feature allows automatic syncing of Grist documents and document versions to S3-compatible stores such as MinIO (or AWS S3 itself). Grist Enterprise has native support for Azure storage accounts, and for AWS S3 using AWS\u2019s official client. It is advisable to have Redis enabled when using cloud storage, since this is the best-tested configuration. Enabling snapshotting results in a big change in how documents are stored, and is best done prior to creating documents. Back up your work before changing this configuration.","title":"Cloud Storage"},{"location":"install/cloud-storage/#s3-compatible-stores-via-minio-client","text":"Turn this on by setting the following environment variables: Set GRIST_DOCS_MINIO_ACCESS_KEY and GRIST_DOCS_MINIO_SECRET_KEY . Set GRIST_DOCS_MINIO_BUCKET to the name of a versioned bucket you have created. It is important that the bucket have versioning enabled. Set GRIST_DOCS_MINIO_ENDPOINT to the appropriate hostname - no protocol, no port. (Optional) Set GRIST_DOCS_MINIO_USE_SSL to 1 to use https protocol (default) or 0 for http . (Optional) Set GRIST_DOCS_MINIO_PORT to the port to use, if the default for the protocol (80/443) isn\u2019t right. (Optional) Set GRIST_DOCS_MINIO_PREFIX to the prefix for your documents, defaults to \u201cdocs/\u201d. (Optional) Set GRIST_DOCS_MINIO_BUCKET_REGION to the region for your bucket, defaults to \u201cus-east-1\u201d. If using AWS S3, the endpoint to use is s3.amazonaws.com , and there\u2019s no need to set a port number or SSL flag. The access and secret keys are your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Make sure to create a versioned bucket in advance.","title":"S3-compatible stores via MinIO client"},{"location":"install/cloud-storage/#azure","text":"For Azure: Create a storage account in the Azure portal. For the storage account\u2019s blob service, make sure that versioning is enabled. Get a connection string from the storage account\u2019s Access Keys section. It may look something like DefaultEndpointsProtocol=https;AccountName=... . Place the connection string in an environment variable called AZURE_STORAGE_CONNECTION_STRING . Set the name of an Azure storage container in an environment variable called GRIST_AZURE_CONTAINER . An example of a container name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_AZURE_PREFIX .","title":"Azure"},{"location":"install/cloud-storage/#s3-with-native-aws-client","text":"For S3: Set the name of the S3 bucket in an environment variable called GRIST_DOCS_S3_BUCKET . An example of a bucket name is my-grist-docs . Set a prefix such as v1/ in an environment variable called GRIST_DOCS_S3_PREFIX . Arrange for access using AWS\u2019s many options; if nothing else, you can set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY variables.","title":"S3 with native AWS client"},{"location":"install/cloud-storage/#usage-once-configured","text":"Once the external storage configuration is in place, start Grist as normal for self-managed Grist. Upon startup, there should be a line like: info: == grist.externalStorage.[s3|azure|minio].active: true All documents will be read from and saved to the corresponding S3 bucket or Azure container. Configuration is simplest on a fresh Grist install without any preexisting Grist documents. Once up and running, it is a good idea to configure the storage account\u2019s \u201clifecycle management\u201d to place any bounds you want on how long versions are retained. Grist has no requirements here, this is strictly to your taste.","title":"Usage once configured"},{"location":"install/grist-connect/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . GristConnect # Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/grist-connect/#gristconnect","text":"Discourse is popular forum software made to be integrated with many sites (including our own ). It is annoying if users need a login for a forum that is separate to their login on the rest of a site. The protocol Discourse created for solving this is quite well done and is called DiscourseConnect . We decided to have Grist support the same protocol, so any software that has plugins for letting Discourse use its logins (such as WordPress) can also let Grist use its logins. This isn\u2019t an official standard so we called our implementation of it \u201cGristConnect\u201d rather than \u201cDiscourseConnect\u201d. Setting up GristConnect requires some settings for Grist, and a plugin or custom coding for your site or SSO. The Grist settings are as follows: GRIST_CONNECT_URL : setting this environment variable will enable GristConnect. It should be an URL address of an external site where users will be redirected on login/signup request. GRIST_CONNECT_SECRET : (required) a secret key, used to sign (hash) query parameters in URLs used during login flow. GRIST_CONNECT_LOGOUT_URL : (optional) an URL address users will be redirected to after signing out. By default - Grist\u2019s existing sign-out page. GRIST_CONNECT_ENDPOINT : (optional) a callback endpoint exposed by Grist where users should be redirected to after a successful login attempt in the external Identity Provider site (default value: /connect/login ). For your site or SSO, you may find that the software you use supports DiscourseConnect, in which case you can enable that and coordinate with the Grist settings. Otherwise, here are the exact details of what needs to happen: When users try to login into Grist, they are redirected to a GRIST_CONNECT_URL with signed query parameters, for example, https://auth-provider.example.com?sso=PAYLOADBASE64&sig=cAlculateDHash The payload is a Base64 encoded query string containing: nonce - a one time token generated by Grist return_url - a URL to redirect the user to after successful login - this always will be set to the full Grist URL for GRIST_CONNECT_ENDPOINT . User should sign in using the GRIST_CONNECT_URL login screen. An external provider should create a new url-encoded payload with the following parameters nonce - (required) a token that was sent from Grist external_id - (required) any string that is unique to the user and will never change email - (required) a valid user email name - (required) a user name avatar_url - (optional) an URL to user\u2019s profile photo. Note that Grist will also accept picture parameter for the same purpose. This payload should be base64 encoded and properly escaped to be a valid query string. A provider should calculate a HMAC_SHA256 hash of a payload. A user should redirect to the GRIST_CONNECT_ENDPOINT endpoint with a signed query string parameters, for example, https://grist.customhost.com/connect/login?sso=ENCODEDPAYLOD&sig=HASH Grist will create a new user or update an existing one. Users will be identified by external_id parameter. The user will be signed into Grist.","title":"GristConnect"},{"location":"install/aws-marketplace/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . AWS Marketplace # Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID. First run setup # After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console: How to log in to the Grist instance # During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button: Custom domain and SSL setup for HTTPS access # Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain. Authentication setup # We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials. Running Grist in a separate VPC # grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed. Updating grist-omnibus # The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus . Other important information # The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#aws-marketplace","text":"Grist on the AWS Marketplace has what you need to run a self-hosted Grist instance with minimal setup, and is based on grist-omnibus . Below are the complete configuration steps, including authentication via OpenID.","title":"AWS Marketplace"},{"location":"install/aws-marketplace/#first-run-setup","text":"After deploying the instance, Grist should be instantly available through the HTTP protocol on an autogenerated domain such as ec2-3-94-254-105.compute-1.amazonaws.com (labeled Public IPv4 DNS by AWS). Default credentials: email: admin@example.getgrist.com password: [instance-id]* * Instance ID can be found on the EC2 page in the AWS Console:","title":"First run setup"},{"location":"install/aws-marketplace/#how-to-log-in-to-the-grist-instance","text":"During deployment, you should have been asked about creating or using key pairs. You can use this pair to log in via SSH from your terminal/bash. The default user for the Grist EC2 instance is named \u201cubuntu\u201d, and you can log in to ubuntu@[ec2-instance-public-ip] . Note: You need to use the *.pem file you received while generating key pairs on AWS. Details about connecting via SSH can be found in the following places: Windows: https://learn.microsoft.com/en-us/windows/terminal/tutorials/ssh Linux: https://www.ssh.com/academy/ssh/command macOS: https://www.servermania.com/kb/articles/ssh-mac If you don\u2019t want to connect via SSH, AWS provides the option to connect from within the AWS console using the \u201cConnect\u201d button:","title":"How to log in to the Grist instance"},{"location":"install/aws-marketplace/#custom-domain-and-ssl-setup-for-https-access","text":"Custom domains are required for secure access to Grist. If you already have an SSL certificate, you can use your own (as described in the grist-omnibus README ). If not, Grist can generate a certificate from Let\u2019s Encrypt. For that, a valid domain and email must be configured: Point the domain to the IP address of the Grist EC2 instance. If you don\u2019t use the Elastic IP service , the instance can have a different public IPv4 address each time it\u2019s started. Log in to the Grist EC2 instance. Set the URL parameter in the grist/gristParameters file. You need administrator privileges to perform this action, so you can open an editor by running sudo nano grist/gristParameters . Run the restartGrist script with sudo ~/grist/restartGrist . Once the above steps are completed, you should be able to access Grist on your custom domain.","title":"Custom domain and SSL setup for HTTPS access"},{"location":"install/aws-marketplace/#authentication-setup","text":"We support Google or Microsoft as OpenID providers. For configuring other authentication providers, please refer to the dex documentation . To configure Grist authentication with Google or Microsoft, you must have an application registered with the corresponding provider: Microsoft: https://learn.microsoft.com/en-us/power-pages/security/authentication/openid-settings Google: https://support.google.com/cloud/answer/6158849?hl=en Once you have your client ID and secret, you\u2019ll need to pass them to the gristParameters file inside the Grist EC2 instance: Log in to the Grist EC2 instance. Open ~/grist/gristParameters . Update the CLIENT_ID and CLIENT_SECRET sections for the relevant provider(s). If you\u2019re using only one provider, leave the second section commented out. Update ADMIN_EMAIL in the same file. It should correspond to the email you will use to log in via your authentication provider. For example: ADMIN_EMAIL=frank@your-organization.com If you want to change your team\u2019s name, update TEAM_NAME in the same file. Run restartGrist with the clean flag using sudo ~/grist/restartGrist clean to clear old login data. Important: This will delete all Grist documents! Once the above has been configured, you should be able to log in with your Google/Microsoft credentials.","title":"Authentication setup"},{"location":"install/aws-marketplace/#running-grist-in-a-separate-vpc","text":"grist-omnibus is designed to work on each account-default VPC. To make it run on a custom VPC, you\u2019ll need to properly configure all VPC elements. For more information on this configuration, read here . To run Grist on a VPC, the following must be properly set up: Assigning a public DNS name to the Grist EC2 instance is allowed. The VPC can be accessed from the internet (allowing internet gateway and routing tables to handle traffic). A security group connection from ports 22 (SSH for configuration), 80 (HTTP connection) and 433 (HTTPS connection) is allowed.","title":"Running Grist in a separate VPC"},{"location":"install/aws-marketplace/#updating-grist-omnibus","text":"The packaged version of grist-omnibus will auto-update before each launch. To update grist-omnibus manually, restart the Grist EC2 instance or log in via SSH and call sudo ~/grist/restartGrist . There are currently no plans to support the Grist AWS Marketplace environment outside of grist-omnibus .","title":"Updating grist-omnibus"},{"location":"install/aws-marketplace/#other-important-information","text":"The Grist EC2 instance should have the \u201cPersistent store\u201d option checked. Grist stores all the data in the ~/grist-persist directory. Deleting this folder will result in a loss of all data from all documents. Do not delete ~/grist-persist/acme.json , as it contains a private key from Let\u2019s Encrypt. Deleting it too often can result in Let\u2019s Encrypt denying issuing further certificates from your domain.","title":"Other important information"},{"location":"telemetry/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Overview of Telemetry # Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of telemetry"},{"location":"telemetry/#overview-of-telemetry","text":"Grist development is guided by telemetry: a set of measurements aimed at quantifying aspects of how Grist is used. A self-managed installation of Grist by default does no telemetry. When telemetry is enabled, data about usage is sent to a service maintained by Grist Labs. Telemetry may be configured by optional environment variables: GRIST_TELEMETRY_LEVEL . This may be off , limited , or full . The default is off . A setting of limited or full results in data being sent to a service operated by Grist Labs. We encourage users to set telemetry to limited so that their usage counts, and guides Grist development. We only recommend a full setting if you have used GRIST_TELEMETRY_URL to redirect telemetry to a service you control. It includes identifiers internal to your installation that we would rather not know. GRIST_TELEMETRY_URL . This controls where telemetry is sent. It defaults to a service operated by Grist Labs. If you are running a large hosted service, you may wish to direct telemetry to a service you control. Telemetry may also be configured interactively by the owner of a Grist installation, see How do I control telemetry? for details. The limited setting results in some coarse-grained telemetry. This level is intended for an installation of Grist that has opted in to providing telemetry. The goal is to understand how Grist is used \u201cin the wild\u201d in terms of feature use and resource counts, without sharing any business data or personal identifiable information. See limited telemetry for details of exactly what is sent. The full setting gives relatively fine-grained telemetry. This level is intended for large hosted services, such as that run by Grist Labs. More information is logged, to facilitate running the service and developing the product. No personal identifiable information is included. Opaque identifiers are included which, on need (for example in a service outage) could be related to personal information through non-telemetric stores. See full telemetry for details of exactly what is sent.","title":"Overview of Telemetry"},{"location":"telemetry-limited/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: limited # This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"Limited telemetry"},{"location":"telemetry-limited/#telemetry-level-limited","text":"This is a telemetry level appropriate for self-hosting instances of Grist. Data is transmitted to Grist Labs.","title":"Telemetry level: limited"},{"location":"telemetry-limited/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-limited/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-limited/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-limited/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. access string The document access level of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-limited/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-limited/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-limited/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player.","title":"watchedVideoTour"},{"location":"telemetry-full/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Telemetry level: full # This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service. apiUsage # Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header. beaconOpen # Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconArticleViewed # Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconEmailSent # Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. beaconSearch # Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. documentForked # Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document. documentOpened # Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated. documentUsage # Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d. processMonitor # Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported. sendingWebhooks # Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. signupVerified # Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any. siteMembership # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site. siteUsage # Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document. tutorialProgressChanged # Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion. tutorialRestarted # Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. watchedVideoTour # Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"Full telemetry"},{"location":"telemetry-full/#telemetry-level-full","text":"This is a telemetry level appropriate for internal use by a hosted service, with GRIST_TELEMETRY_URL set to an endpoint controlled by the operator of the service.","title":"Telemetry level: full"},{"location":"telemetry-full/#apiusage","text":"Triggered when an HTTP request with an API key is made. Field Type Description method string The HTTP request method (e.g. GET, POST, PUT). userId number The id of the user that triggered this event. userAgent string The User-Agent HTTP request header.","title":"apiUsage"},{"location":"telemetry-full/#beaconopen","text":"Triggered when HelpScout Beacon is opened. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconOpen"},{"location":"telemetry-full/#beaconarticleviewed","text":"Triggered when an article is opened in HelpScout Beacon. Field Type Description articleId string The id of the article. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconArticleViewed"},{"location":"telemetry-full/#beaconemailsent","text":"Triggered when an email is sent in HelpScout Beacon. Field Type Description userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconEmailSent"},{"location":"telemetry-full/#beaconsearch","text":"Triggered when a search is made in HelpScout Beacon. Field Type Description searchQuery string The search query. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"beaconSearch"},{"location":"telemetry-full/#documentforked","text":"Triggered when a document is forked. Field Type Description docIdDigest string A hash of the doc id. siteId number The id of the site containing the forked document. siteType string The type of the site. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. forkIdDigest string A hash of the fork id. forkDocIdDigest string A hash of the full id of the fork, including the trunk id and fork id. trunkIdDigest string A hash of the trunk id. isTemplate boolean Whether the trunk is a template. lastActivity date Timestamp of the last update to the trunk document.","title":"documentForked"},{"location":"telemetry-full/#documentopened","text":"Triggered when a public document or template is opened. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. isPublic boolean Whether the document is public. isSnapshot boolean Whether a snapshot was opened. isTemplate boolean Whether the document is a template. lastUpdated date Timestamp of when the document was last updated.","title":"documentOpened"},{"location":"telemetry-full/#documentusage","text":"Triggered on doc open and close, as well as hourly while a document is open. Field Type Description docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event. triggeredBy string What caused this event to trigger. May be either \u201cdocOpen\u201d, \u201cinterval\u201d, or \u201cdocClose\u201d. isPublic boolean Whether the document is public. rowCount number The number of rows in the document. dataSizeBytes number The total size of all data in the document, excluding attachments. attachmentsSize number The total size of all attachments in the document. numAccessRules number The number of access rules in the document. numUserAttributes number The number of user attributes in the document. numAttachments number The number of attachments in the document. attachmentTypes string[] A list of unique file extensions compiled from all of the document\u2019s attachments. numCharts number The number of charts in the document. chartTypes string[] A list of chart types of every chart in the document. numLinkedCharts number The number of linked charts in the document. numLinkedWidgets number The number of linked widgets in the document. numColumns number The number of columns in the document. numColumnsWithConditionalFormatting number The number of columns with conditional formatting in the document. numFormulaColumns number The number of formula columns in the document. numTriggerFormulaColumns number The number of trigger formula columns in the document. numSummaryFormulaColumns number The number of summary formula columns in the document. numFieldsWithConditionalFormatting number The number of fields with conditional formatting in the document. numTables number The number of tables in the document. numOnDemandTables number The number of on-demand tables in the document. numTablesWithConditionalFormatting number The number of tables with conditional formatting in the document. numSummaryTables number The number of summary tables in the document. numCustomWidgets number The number of custom widgets in the document. customWidgetIds string[] A list of plugin ids for every custom widget in the document. The ids of widgets not created by Grist Labs are replaced with \u201cexternalId\u201d.","title":"documentUsage"},{"location":"telemetry-full/#processmonitor","text":"Triggered every 5 seconds. Field Type Description heapUsedMB number Size of JS heap in use, in MiB. heapTotalMB number Total heap size, in MiB, allocated for JS by V8. cpuAverage number Fraction (typically between 0 and 1) of CPU usage. Includes all threads, so may exceed 1. intervalMs number Interval (in milliseconds) over which cpuAverage is reported.","title":"processMonitor"},{"location":"telemetry-full/#sendingwebhooks","text":"Triggered when sending webhooks. Field Type Description numEvents number The number of events in the batch of webhooks being sent. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"sendingWebhooks"},{"location":"telemetry-full/#signupverified","text":"Triggered after a user successfully verifies their account during sign-up. Not triggered in grist-core. Field Type Description isAnonymousTemplateSignup boolean Whether the user viewed any templates before signing up. templateId string The doc id of the template the user last viewed before signing up, if any.","title":"signupVerified"},{"location":"telemetry-full/#sitemembership","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. numOwners number The number of users with an owner role in this site. numEditors number The number of users with an editor role in this site. numViewers number The number of users with a viewer role in this site.","title":"siteMembership"},{"location":"telemetry-full/#siteusage","text":"Triggered daily. Field Type Description siteId number The site id. siteType string The site type. inGoodStanding boolean Whether the site\u2019s subscription is in good standing. stripePlanId string The Stripe Plan id associated with this site. numDocs number The number of docs in this site. numWorkspaces number The number of workspaces in this site. numMembers number The number of site members. lastActivity date A timestamp of the most recent update made to a site document.","title":"siteUsage"},{"location":"telemetry-full/#tutorialprogresschanged","text":"Triggered on changes to tutorial progress. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. lastSlideIndex number The 0-based index of the last tutorial slide the user had open. numSlides number The total number of slides in the tutorial. percentComplete number Percentage of tutorial completion.","title":"tutorialProgressChanged"},{"location":"telemetry-full/#tutorialrestarted","text":"Triggered when a tutorial is restarted. Field Type Description tutorialForkIdDigest string A hash of the tutorial fork id. tutorialTrunkIdDigest string A hash of the tutorial trunk id. docIdDigest string A hash of the doc id. siteId number The site id. siteType string The site type. altSessionId string A random, session-based identifier for the user that triggered this event. access string The document access level of the user that triggered this event. userId number The id of the user that triggered this event.","title":"tutorialRestarted"},{"location":"telemetry-full/#watchedvideotour","text":"Triggered when the video tour is closed. Field Type Description watchTimeSeconds number The number of seconds elapsed in the video player. userId number The id of the user that triggered this event. altSessionId string A random, session-based identifier for the user that triggered this event.","title":"watchedVideoTour"},{"location":"newsletters/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist for the Mill # Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Newsletters"},{"location":"newsletters/#grist-for-the-mill","text":"Welcome to our monthly newsletter of updates and tips for Grist users. The phrase \u201cGrist for the mill\u201d means \u201cuseful experience, material, or knowledge\u201d, perfectly reflecting its purpose. To receive the newsletter, sign up for Grist . September 2024 Newsletter : Two-way references, Grist Desktop release, cool community creations. August 2024 Newsletter : Markdown in the cell, custom widget cards, and webhook documentation. July 2024 Newsletter : Cumulative functions, new kinds of lookups, improved variable syncing and a new users webinar! June 2024 Newsletter : Research templates, running rootless, and community contributions! May 2024 Newsletter : New Grist Business plan, a formula timer utility, draggable conditionals and admin console improvements! April 2024 Newsletter : Filtering reference and choice dropdown lists, an admin console for self-hosters. March 2024 Newsletter : Improvements to forms, new file formats, and a boot page for self-hosters. February 2024 Newsletter : Community spotlight: new widgets, fun experiments, and in-depth explorations. January 2024 Newsletter : Grist Forms are here! Getting data into Grist just got easier. December 2023 Newsletter : Grist 2023 year in review, forms user testing and community showcase. November 2023 Newsletter : Open referenced records with a click, hang out with us on Discord, and add column types more easily. October 2023 Newsletter : New formula shortcuts, two experimental widgets, colorful calendar events and much more! September 2023 Newsletter : Calendar widget, two new templates, and API endpoint for making SQL queries. August 2023 Newsletter : Grist CSV Viewer! Llama AI support! July 2023 Newsletter : AI Formula Assistant launched! June 2023 Newsletter : Selector row highlighting, new templates, and community contributions. May 2023 Newsletter : Column and widget description, webhooks, and a vote for the best flashcards. April 2023 Newsletter : Flashcards contest, a way to prank friends with Grist, and a new experiment in publishing spreadsheet data. March 2023 Newsletter : Minimizing widgets, in-product tutorials, and a flashcards custom widget! February 2023 Newsletter : Grist in more languages, and a sneak peek into dev passion projects. January 2023 Newsletter : Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas, and Deutsch! Plus expanding widgets and access rules improvements. December 2022 Newsletter : New date filter with calendar, and snapshots in grist-core. November 2022 Newsletter : Grist experiment with AI formula writing, and improved sort and filter. October 2022 Newsletter : Quick sums, duplicate tables, and new API methods. September 2022 Newsletter : Dark Mode \ud83d\udd76 + improved user management. August 2022 Newsletter : Free team sites, conditional row styles, and better formula help. July 2022 Newsletter : Formula cheat sheet and summary tables in raw data. June 2022 Newsletter : Filtering by range, and new PEEK() function. May 2022 Newsletter : Raw data tables, and new summary table linking. April 2022 Newsletter : Rich text editor, and more cell styles. March 2022 Newsletter : Conditional formatting, new API method, and the new Sprouts Program. February 2022 Newsletter : Custom widgets menu, 2FA, access rules improvements, and cell context menu. January 2022 Newsletter : Managing template document tours, and 4 new templates, including an inventory manager. December 2021 Newsletter : Zapier instant triggers, and four new templates. November 2021 Newsletter : Import column mapping, filter on hidden columns, and more sorting options. October 2021 Newsletter : Editing choices, inline links, and enhanced import previews. September 2021 Newsletter : Improved imports, global currencies, and more ways to integrate. August 2021 Newsletter : Reference Lists, Embedding, Templates, Pabbly, and more. July 2021 Newsletter : Colors, Time and Authorship Stamps, and Google Sheets Integration. June 2021 Newsletter : Introducing Freeze Columns and a timesheet-tracking template. May 2021 Newsletter : Choice Lists, 3-step Tutorial on Reference Columns. April 2021 Newsletter : A 4-Step Guide to Link Keys. March 2021 Newsletter : Access Rules. February 2021 Newsletter : Improved Mobile Support, Totals. January 2021 Newsletter : Task Management, Find a Consultant, Be a Consultant. December 2020 Newsletter : Maps, Access Control, Compare Previous. November 2020 Newsletter : Open Source, Improved Attachments, Compare Versions. October 2020 Newsletter : Printing Support, Mailing Labels, Open Source Beta. September 2020 Newsletter : Public Access, Payroll Tracking. August 2020 Newsletter : Invoices, Custom Widgets, Document Lists. July 2020 Newsletter : Number Format Options, Prepare Emails with Formulas. June 2020 Newsletter : Work on Complex Changes, Automatic Backups. May 2020 Newsletter : Copy as Template, Better URLs, NY Tech Meetup.","title":"Grist for the Mill"},{"location":"newsletters/2024-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Two-way references # References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many. Grist Desktop 0.3.0 # The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub . Formula Assistant model updates # We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio . Community highlights # \ud83d\udd28 Grist hackathon # Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know! \u267b\ufe0f Grist reusable code repository # Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns. \ud83e\uddb8\u200d\u2640\ufe0f Super dashboards # Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix . \u2705 Change tracking with trigger formulas # We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \ud83d\udd04 Two-Way References # They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR \u2728 New Feature Showcase # In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/09"},{"location":"newsletters/2024-09/#september-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2024 Newsletter"},{"location":"newsletters/2024-09/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-09/#two-way-references","text":"References now support twice the ways! Two-way references are a powerful tool that we\u2019ve found ourselves using for projects even while in development. As the overly-complex GIF above demonstrates, with the click of a button you can now add a two-way reference to a reference column. And what exactly happens when you add a two-way reference? Get ready for the word \u201creference\u201d a lot\u2026 A new reference column is created in the target table. Essentially, a \u201creverse reference column\u201d. This column is automatically populated with references from the table where the two-way reference was created. Any reference changes between the two columns are automatically synchronized. Once you get a hang of their particularities, two-way references open up a new field of data relations. Learn more about two-way references in the Help Center . Need a primer on what, exactly, references are, and where you might find relationships in your data? Take a look at our new blog post to learn about the Big Four: one-to-one, one-to-many, many-to-one, and last but not least: many-to-many.","title":"Two-way references"},{"location":"newsletters/2024-09/#grist-desktop-030","text":"The summer is over, but the hard work of our summer intern Leslie continues to bear fruit. This release makes Grist Desktop behave more like a desktop app than ever before, as part of our \u201cmake Grist Desktop feel more normal\u201d initiative. How normal you ask? You can open and save files anywhere on the filesystem, for example. That\u2019s extremely normal, and things just get more normal from there\u2026 Check out the latest release on GitHub .","title":"Grist Desktop 0.3.0"},{"location":"newsletters/2024-09/#formula-assistant-model-updates","text":"We\u2019ve updated the model used by the AI Formula Assistant from gpt-3.5-turbo to gpt-4o . Based on our internal testing, we\u2019re seeing a modest increase in assistant accuracy. For self-hosters, the Formula Assistant now functions against any OpenAI chat completion endpoints ending in /v1/chat/completions . We\u2019ve also tested using the Formula Assistant via local models through LM Studio .","title":"Formula Assistant model updates"},{"location":"newsletters/2024-09/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-09/#grist-hackathon","text":"Longtime Grist partners ANCT and DINUM joined forces with more members of Grist\u2019s French-speaking community to form the French Grist Federation. The new group hosted a hackathon last weekend in Paris as a branch of Code For France , which aims to develop a transparent and participatory digital commons. Read a summary of the hackathon at Open Source Politics , auto-translated from French. Hosting a hackathon of your own? Please let us know!","title":"\ud83d\udd28 Grist hackathon"},{"location":"newsletters/2024-09/#grist-reusable-code-repository","text":"Users on Grist\u2019s Community Forum have started collaborating on a shared user code repository , which already hosts cool utilities such as TomNit\u2019s self-sanitizing reference fields . It\u2019s also a great example of how Grist can be extended via Python classes, essentially storing function \u201clibraries\u201d in formula columns.","title":"\u267b\ufe0f Grist reusable code repository"},{"location":"newsletters/2024-09/#super-dashboards","text":"Do you use lots of charts? We support you and celebrate you. Still, Grist can get a bit cramped with several charts. To work around this, Rogerio has created \u201csuper dashboards\u201d which use the HTML widget and scrollable iframes to go from this: To this: For more heroic uses of the HTML widget, also check out Rogerio\u2019s thread on creating a dynamic risk matrix .","title":"\ud83e\uddb8\u200d\u2640\ufe0f Super dashboards"},{"location":"newsletters/2024-09/#change-tracking-with-trigger-formulas","text":"We saw above how trigger formulas can help sanitize references, and here\u2019s another handy use case. Rogerio is back with a solution for simply tracking changes made to columns, including timestamps. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"\u2705 Change tracking with trigger formulas"},{"location":"newsletters/2024-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-09/#webinar-two-way-references","text":"They\u2019re heeereee \ud83d\udc7b\u2026 Spooky season comes a bit early with a feature that\u2019s haunted us for a while now: two-way references. Link data in both directions, opening up new and powerful ways to leverage relational data. Now it\u2019s time to be haunted by possibilities. Join us in October to explore how! Thursday October 10 at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: \ud83d\udd04 Two-Way References"},{"location":"newsletters/2024-09/#new-feature-showcase","text":"In September, Natalie caught us up on the latest Grist features that you may have missed, including markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these tools can enhance your existing Grist docs, or give you new ideas for future workflows. WATCH SEPTEMBER\u2019S RECORDING","title":"\u2728 New Feature Showcase"},{"location":"newsletters/2024-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-09/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Markdown cell formatting # It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel: New Custom Widget flow \ud83c\udccf # Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions . Webhooks documentation # Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities. GitHub contribution templates # To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests. Self-hosters: OIDC enhancements # We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements. GitLocalize translations for Grist documentation # Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f Community highlights # PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: \u2728 New Feature Showcase # In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Grist 101 # In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/08"},{"location":"newsletters/2024-08/#august-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2024 Newsletter"},{"location":"newsletters/2024-08/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-08/#markdown-cell-formatting","text":"It\u2019s finally here! Text columns now support Markdown. This means that you can use nearly all Markdown syntax (apart from images and horizontal rules) in any cell \u2013 and even in card widgets! To enable Markdown in any Text column, simply select the option in the \u201cCell Format\u201d dropdown in the Creator Panel:","title":"Markdown cell formatting"},{"location":"newsletters/2024-08/#new-custom-widget-flow","text":"Adding custom widgets now has a new look! We\u2019ve dropped the dropdown, and embraced a more spacious card-based UX. Retaining the same functionality as before, this new flow also highlights community-developed widgets, making it easier for us to include third-party widgets in the future. For self-hosters, you can also add to this list (or make your own) following these instructions .","title":"New Custom Widget flow \ud83c\udccf"},{"location":"newsletters/2024-08/#webhooks-documentation","text":"Webhooks are powerful. So powerful, in fact, that creating proper documentation for them took us over a year. \ud83e\udee0 But your patience is rewarded with thorough documentation by Jordi (our Systems Developer), who used his database of homebrew NES games to get to the bottom of webhooks and their particularities.","title":"Webhooks documentation"},{"location":"newsletters/2024-08/#github-contribution-templates","text":"To help make contributing to grist-core easier and more consistent, Florent (inspired by PeerTube ) created several templates for bug reporting, upgrade issues and feature requests.","title":"GitHub contribution templates"},{"location":"newsletters/2024-08/#self-hosters-oidc-enhancements","text":"We\u2019ve added some extra security options for OIDC authentication, enabling compatibility with new providers that have specific requirements.","title":"Self-hosters: OIDC enhancements"},{"location":"newsletters/2024-08/#gitlocalize-translations-for-grist-documentation","text":"Our friends and contributors at ANCT have enabled GitLocalize for the Grist Help Center . You can see a machine-translated demo of the full docs en Fran\u00e7ais . To contribute, check out Vincent\u2019s how-to post in the Community Forum . For translating Grist\u2019s UI, we still use Weblate \u2013 and are always looking for more help! A hearty thanks to all our contributors who have helped make Grist readable around the world. \ud83c\udf0f","title":"GitLocalize translations for Grist documentation"},{"location":"newsletters/2024-08/#community-highlights","text":"PyGrister , the Python client from Riccardo Polignieri mentioned last May, has been updated. It\u2019s now on PyPi , and has thorough documentation to boot. \ud83d\udcdd A new Catalan translation has just kicked off on Weblate, thanks to xmontero! See here for more information on helping translate Grist. Antol Peshkov has created a simple custom dropdown widget that does exactly what it sounds like. Note: if you\u2019re self-hosting, a fix related to this widget is on it\u2019s way to grist-core soon! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-08/#webinar-new-feature-showcase","text":"In September, catch up on the latest Grist features that you may have missed, including Markdown in cells, cumulative functions, the formula timer, and record cards. Learn how these new tools can enhance your existing Grist docs, or give you new ideas for future workflows. Thursday September 19 at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: \u2728 New Feature Showcase"},{"location":"newsletters/2024-08/#grist-101","text":"In August, Natalie hosted an introductory webinar designed to help new users navigate the basics of Grist. This session provided users with the essential tools and knowledge to get started. We covered key features and best practices to maximize your productivity. Perfect for beginners! WATCH AUGUST\u2019S RECORDING","title":"Grist 101"},{"location":"newsletters/2024-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-08/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s new # Cumulative functions: PREVIOUS() , NEXT() and RANK() # If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center . New kinds of lookups: find.* methods # Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center . Tutorial progress # If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8 Grist Enterprise: now a toggle! # For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details. Grist ActivePieces integration # Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request ! Improved column rename syncing # A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically! Fly.io build previews for external contributors # If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code. User spotlight \u2013 Callum Spawforth/Savage Game Design # When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database. Community highlights # A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Grist 101: A New User\u2019s Guide # Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Sharing Partial Data with Link Keys # In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/07"},{"location":"newsletters/2024-07/#july-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2024 Newsletter"},{"location":"newsletters/2024-07/#whats-new","text":"","title":"What’s new"},{"location":"newsletters/2024-07/#cumulative-functions-previous-next-and-rank","text":"If you\u2019ve ever tried to get a running total in a Grist table, or access previous values for calculations, we have some good news. We\u2019ve added a series of new functionality related to cumulative functions that greatly expand what you can do in the grid: PREVIOUS() finds the previous record in a full table. NEXT() is the same, but in the other direction. RANK() returns the rank of the record within the group, starting with 1. Check out our complete documentation in our Help Center .","title":"Cumulative functions: PREVIOUS(), NEXT() and RANK()"},{"location":"newsletters/2024-07/#new-kinds-of-lookups-find-methods","text":"Even for a Python pro, it can be tricky to look up items based on nearness to a threshold, especially if the lookup needs to be efficient. These new find.* methods make it far easier. Essentially, lookupRecords() now allows search in sorted results! Here\u2019s an example, showing the old way (\ud83d\udc22\ud83d\ude45) and the new way (\ud83c\udfc3\u200d\u27a1\ufe0f): More details: The find.* methods are le , lt , ge , gt , and eq (less than or equal, less than, greater than or equal, greater than, equal). The order_by argument now supports tuples, as well as the \u201c-\u201d prefix to reverse order, e.g. (\"Category\", \"-Date\") . See the full documentation in our Help Center .","title":"New kinds of lookups: find.* methods"},{"location":"newsletters/2024-07/#tutorial-progress","text":"If you\u2019re reading this, you probably don\u2019t need to complete our basics tutorial. Still, we wanted to show off this cool new progress bar and hint at further onboarding improvements to help get new users up to speed! You are also now approximately 38% through this newsletter. Good job! \ud83d\udcc8","title":"Tutorial progress"},{"location":"newsletters/2024-07/#grist-enterprise-now-a-toggle","text":"For our large enterprise users, installing Grist just got easier: you can now use the standard Docker image! We\u2019ve consolidated images so that enabling and activating Enterprise is done in the admin console. This also means non-Enterprise users can trial Enterprise features like GristConnect and Azure/S3 storage for 30 days. The full Enterprise plan also includes installation support and professional services provided by our Grist experts. Check out our Pricing page for details.","title":"Grist Enterprise: now a toggle!"},{"location":"newsletters/2024-07/#grist-activepieces-integration","text":"Open source automation tool ActivePieces now has a bunch of Grist-specific actions & triggers . Thanks to Kishan Parmar for the pull request !","title":"Grist ActivePieces integration"},{"location":"newsletters/2024-07/#improved-column-rename-syncing","text":"A small but mighty fix that addresses several usability frustrations across Grist. Now, when you rename a column, associated access control and conditional formatting rules update automatically!","title":"Improved column rename syncing"},{"location":"newsletters/2024-07/#flyio-build-previews-for-external-contributors","text":"If you\u2019re a grist-core contributor working on a PR, we now have automatic previews for your work. Everyone can now see live demos, which should help move the review process along! We use Fly.io to run a self-hosted instance of Grist including your code.","title":"Fly.io build previews for external contributors"},{"location":"newsletters/2024-07/#user-spotlight-callum-spawforthsavage-game-design","text":"When he isn\u2019t working on Grist, Callum ( @Spoffy on GitHub) works with the team at Savage Game Design to develop Vietnam War-era video games. The Savage team and their military advisors strive to make their game historically accurate, which means keeping track of a ton of real-world events, places, people, units, etc\u2026 and then relating them all back to each other. The team uses Grist to build an event database as they create their storylines, treating it as a \u201cstarting point to map out what [they] want to happen during a certain time period.\u201d As storylines evolve and become more complex, they connect their Grist documents with other tools like Google Docs, maintaining a link with their single source of truth. Instead of just having a Game Design Document, they now also have a Game Design Database.","title":"User spotlight \u2013 Callum Spawforth/Savage Game Design"},{"location":"newsletters/2024-07/#community-highlights","text":"A new Basque translation is nearly complete, thanks to xabirequejo! See here for more information on helping translate Grist. If you\u2019ve used our Proposals & Contracts template (or even followed our tutorial on how to build it!), you\u2019ll be interested to see this updated Markdown widget templating function from Discord user celine de france. This function is an easier way to replace fields within a template (even nested within references!), effectively automating the process for most cases. See their original post here , or check out a live demo . Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-07/#webinar-grist-101-a-new-users-guide","text":"Join us for an introductory webinar designed to help new users navigate the basics of Grist. This session will provide you with the essential tools and knowledge to get started. We\u2019ll cover key features and best practices to maximize your productivity. Perfect for beginners, this webinar will set you on the path to becoming a Grist pro. Don\u2019t miss out\u2014reserve your spot today! Thursday August 15 at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Grist 101: A New User\u2019s Guide"},{"location":"newsletters/2024-07/#sharing-partial-data-with-link-keys","text":"In July, Natalie explained how to use one of Grist\u2019s coolest and least explored features: link keys. We covered how to use Grist\u2019s link keys to share partial data - even just a single row - with third parties. WATCH JULY\u2019S RECORDING","title":"Sharing Partial Data with Link Keys"},{"location":"newsletters/2024-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-07/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f New research templates \ud83d\udc69\u200d\ud83d\udd2c # We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management Self-hosters: you can now run Grist rootless # It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details. Grist Desktop has been updated (and renamed)! # Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40 Community highlights # Translation update # Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist. OpenAPI \ud83e\udd1d Grist # At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura. Grist chat interface with card lists \ud83d\udcac # On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d Custom widget creations \ud83e\udde9 # The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Link Keys # In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Reference Columns # In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"2024/06"},{"location":"newsletters/2024-06/#june-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2024 Newsletter"},{"location":"newsletters/2024-06/#whats-new","text":"Another month where the Grist community provides us with a bounty of cool contributions, for which we are constantly grateful. Keep an eye out for some sensational things we have simmering for this summer. \u2600\ufe0f","title":"What’s New"},{"location":"newsletters/2024-06/#new-research-templates","text":"We\u2019ve created three new templates specifically for research labs . This is no coincidence, as we continue to hear from scientists and technicians who find Grist a welcome addition to their software stack. \ud83e\uddea Sample management \ud83d\udccb Project management \ud83d\udce6 Inventory management","title":"New research templates \ud83d\udc69\u200d\ud83d\udd2c"},{"location":"newsletters/2024-06/#self-hosters-you-can-now-run-grist-rootless","text":"It is now much easier to switch from gVisor to Pyodide sandboxing, which allows you to run a Grist container as a regular user without administrator privileges. See this PR for full details.","title":"Self-hosters: you can now run Grist rootless"},{"location":"newsletters/2024-06/#grist-desktop-has-been-updated-and-renamed","text":"Grist Desktop (formerly Grist Electron) has received a significant update! There\u2019s a new Windows portable build, as well as bug fixes and a grist-core update. Check out the full release notes and download Grist Desktop on GitHub . Look for more Grist Desktop news in the coming months\u2026 \ud83d\udc40","title":"Grist Desktop has been updated (and renamed)!"},{"location":"newsletters/2024-06/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-06/#translation-update","text":"Translations into Finnish, Vietnamese, Bulgarian and Slovak are well underway! A huge thanks to all our contributors, and see here for more information on helping translate Grist.","title":"Translation update"},{"location":"newsletters/2024-06/#openapi-grist","text":"At the top of Grist\u2019s API console is a link to a YML file containing Grist\u2019s OpenAPI spec (note, not OpenAI). We\u2019re thrilled to see it used in some exciting community projects: ben-pr-p\u2019s grist-js , a new TypeScript client. yala1\u2019s GraphQL integration using Hasura.","title":"OpenAPI \ud83e\udd1d Grist"},{"location":"newsletters/2024-06/#grist-chat-interface-with-card-lists","text":"On Discord , celine de france shared a novel implementation of card lists that functions as a native chat/comments feature within Grist, complete with manual read receipts and emoji reacts. \u2705\ud83d\udc4d","title":"Grist chat interface with card lists \ud83d\udcac"},{"location":"newsletters/2024-06/#custom-widget-creations","text":"The humble HTML viewer widget holds many a secret. nicobako discovered that you can execute scripts like Mermaid.js to create fancy charts and diagrams. You can now have the notepad custom widget export regular old HTML to another column, thanks to guillett\u2019s recent PR . Paul (Grist Labs CTO) is not exactly a \u201ccommunity member\u201d (sorry Paul \ud83d\ude22), but his guide to getting custom widgets running offline may be helpful for wannabe widget wizards. Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Custom widget creations \ud83e\udde9"},{"location":"newsletters/2024-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-06/#webinar-link-keys","text":"In July, we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data \u2013 even just a single row \u2013 with third parties. Thursday July 25 at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Link Keys"},{"location":"newsletters/2024-06/#reference-columns","text":"In June, Natalie dove into what references are, how reference columns work in Grist, and explained how to use them to build productive dashboards using the Event Speakers Template . WATCH JUNE\u2019S RECORDING","title":"Reference Columns"},{"location":"newsletters/2024-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-06/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community Forum , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Grist Business plan # We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents. Formula timer # Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b Ordering conditional styles (with bonus draggability) # You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously! Self-hosting admin console improvements # Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most! Community highlights # marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference Columns # In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Reference and Choice Dropdown List Filtering # In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/05"},{"location":"newsletters/2024-05/#may-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2024 Newsletter"},{"location":"newsletters/2024-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-05/#new-grist-business-plan","text":"We\u2019ve been working hard to make sure Grist suits larger teams with complex and sensitive data. To address this segment, we\u2019ve updated our Pricing page to include a new Business plan \u2014 with a Self-Managed flavor! We\u2019re hoping this new plan increases the transparency of pricing around Grist deployments that could use more of our support, rather than requiring organizations to reach out directly for pricing info. There\u2019s also a new option to subscribe annually. New Pro subscriptions are now $10 on the monthly plan, but still at $8 if paying annually. Existing Pro subscriptions are not affected by this change. We\u2019ve also updated our Professional Services page to include some new opportunities we have regarding getting expert help building Grist documents.","title":"New Grist Business plan"},{"location":"newsletters/2024-05/#formula-timer","text":"Enough is enough \u2013 it\u2019s time to put your formulas through their paces. \ud83c\udfc3\u200d\u2640\ufe0f\u23f1\ufe0f It\u2019s a common event to see a spreadsheet collapse under its own weight, like a dying star composed of conditionals. Even mighty Grist documents can slow down when the power of Python goes unchecked. But now there\u2019s a way to diagnose your formulas before things go supernova. Introducing the formula timer , which lets you time formulas for specific changes or for a full document reload. For more information, read all about saving milliseconds with the formula timer \u2014 written by one of the newest additions to the Grist Labs team, Jordan! \ud83d\udc4b","title":"Formula timer"},{"location":"newsletters/2024-05/#ordering-conditional-styles-with-bonus-draggability","text":"You can now easily reorder conditional styles, on both rows and columns! Don\u2019t ask how this was accomplished previously!","title":"Ordering conditional styles (with bonus draggability)"},{"location":"newsletters/2024-05/#self-hosting-admin-console-improvements","text":"Our new admin console is continuing to grow and develop as we work towards our ambitions of easier self-hosting. \ud83d\udd1c The app\u2019s authentication method is now included in the \u201cSecurity Settings\u201d section. If not set up, you\u2019ll be warned. \u270b\ud83e\udd28 Boot panel checks are now visible (and expanded/improved based on your feedback!). We\u2019re still iterating on this, so please let us know on Discord if you have any more feedback. To make the panel more robust, it now honors emergency access via a GRIST_BOOT_KEY setting if authentication is broken, and should even be usable if the app has some common misconfigurations. This means you can count on the admin panel not becoming inaccessible just when you need it most!","title":"Self-hosting admin console improvements"},{"location":"newsletters/2024-05/#community-highlights","text":"marc.fargas has provided our community with a excellently detailed walkthrough of his Grist \u2192 PDF rendering pipeline. The solution does require external services (which may not be completely free), but it\u2019s an great example of extending Grist. Riccardo_Polignieri has created a Python wrapper for Grist\u2019s API called PyGrister ! Get more Python in your workflow! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-05/#webinar-reference-columns","text":"In June, we\u2019ll dive into what references are, how reference columns work in Grist, and explain how to use them to build productive dashboards using the Event Speaker Template . Thursday June 20 at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Reference Columns"},{"location":"newsletters/2024-05/#reference-and-choice-dropdown-list-filtering","text":"In May, Anais returned to look at how filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. See how to take advantage of one of Grist\u2019s newest and most sneakily-powerful features. WATCH MAY\u2019S RECORDING","title":"Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-05/#we-are-here-to-support-you","text":"Professional services. Grist often surprises people with its capabilities. Schedule a free call to assess your needs and help connect you with a Grist expert. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Promoting your solutions built in Grist # Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD What\u2019s New # Filtering reference and choice dropdown lists # When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how. Use as table headers shortcut # Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29 Create new team sites in self-hosted Grist # Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d Admin console for self-hosters # The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89 Networking improvements # Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot! Community highlights # @v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel ! Learning Grist # Webinar: Reference and Choice Dropdown List Filtering # Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR AI Formula Assistant Best Practices # In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING Migrate from Spreadsheet.com # In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/04"},{"location":"newsletters/2024-04/#april-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2024 Newsletter"},{"location":"newsletters/2024-04/#promoting-your-solutions-built-in-grist","text":"Some users have asked us how they can sell solutions they have built in Grist. Often they\u2019re expert in a particular problem, have solved it in Grist, and want to help others solve it too. Before building our own Grist marketplace, we want to experiment on an existing marketplace for templates and solutions services \u2014 Gumroad: https://gumroad.com/ If you\u2019re interested in selling Grist solutions, we\u2019d like to partner with you. Join our Discord and share your thoughts in the #sellers channel. LEARN MORE ON DISCORD","title":"Promoting your solutions built in Grist"},{"location":"newsletters/2024-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-04/#filtering-reference-and-choice-dropdown-lists","text":"When entering data into a reference or choice column you see a dropdown list of all available values to choose from. Sometimes the list can get long or confusing. You can now filter reference and choice columns\u2019 dropdown lists by setting a condition. Learn how.","title":"Filtering reference and choice dropdown lists"},{"location":"newsletters/2024-04/#use-as-table-headers-shortcut","text":"Often when importing data from Excel to Grist, the first row contains what should be the column headers. Manually setting the correct column headers can be time consuming, but not anymore! Thank you to Camille Legeron ( @CamilleLegeron ) from the ANCT team who added a nifty shortcut to copy the values in a row to their respective columns\u2019 labels. \ud83e\udd29","title":"Use as table headers shortcut"},{"location":"newsletters/2024-04/#create-new-team-sites-in-self-hosted-grist","text":"Thanks again to Camille Legeron who made it possible to create additional team sites in self-hosted Grist. To create a new site, select \u201cCreate new team site\u201d from the user or site menu, specify the name and domain of the new site, and click \u201cCreate site.\u201d","title":"Create new team sites in self-hosted Grist"},{"location":"newsletters/2024-04/#admin-console-for-self-hosters","text":"The admin console for self-hosters now includes sandbox settings information and a checkbox to automatically check for Grist release updates. \ud83c\udf89","title":"Admin console for self-hosters"},{"location":"newsletters/2024-04/#networking-improvements","text":"Contributor Jonathan Perret ( @jonathanperret ) implemented a fallback mechanism for networks where WebSocket traffic is not allowed. If you or a colleague have had trouble using Grist behind a corporate network in the past, give it another shot!","title":"Networking improvements"},{"location":"newsletters/2024-04/#community-highlights","text":"@v1adimirn0va built a custom widget that displays data on a timeline. If you\u2019re in our Discord server, check it out here ! If you\u2019re not in our Discord, join our server! Andreas Kl\u00f6ckner ( @inducer on Github) built a tool to collect form responses and save them to existing records in a Grist document. Check it out here ! Working on something cool with Grist? Let us know by posting in the Showcase forum or our #grist-showcase Discord channel !","title":"Community highlights"},{"location":"newsletters/2024-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-04/#webinar-reference-and-choice-dropdown-list-filtering","text":"Filtering a reference or choice column\u2019s dropdown list is very useful in a variety of cases. In May, we\u2019ll show you how to write dropdown condition formulas for the most common scenarios. Thursday May 16 at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Reference and Choice Dropdown List Filtering"},{"location":"newsletters/2024-04/#ai-formula-assistant-best-practices","text":"In April we covered Grist\u2019s AI Formula Assistant which simplifies the hardest part of spreadsheets \u2014 formulas. In this webinar we share tips on how to get the most out of the assistant. This was also Michael\u2019s first time leading a webinar! Michael joined the Grist team as a Solutions Engineer in March. WATCH APRIL\u2019S RECORDING","title":"AI Formula Assistant Best Practices"},{"location":"newsletters/2024-04/#migrate-from-spreadsheetcom","text":"In case you missed it, in March we built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improvements to Grist Forms # Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state. Imports and exports - two new file formats! # DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu. Grist boot page # An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it. Migrate from Spreadsheet.com # We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here ! Community highlights # @tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here . Learning Grist # Webinar: AI Formula Assistant Best Practices # Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Controlling spreadsheet chaos # In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/03"},{"location":"newsletters/2024-03/#march-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2024 Newsletter"},{"location":"newsletters/2024-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-03/#improvements-to-grist-forms","text":"Sections You can now add sections to Grist Forms. To add a section to your form, click the Add New button at the bottom of each section, and then click \u201cInsert section above\u201d or \u201cInsert section below\u201d. Choice and reference select When filling out a form, you can now search for values in long choice and reference select fields, making it easier to find the exact option you\u2019re looking for. Quick embed code If you\u2019d like to embed a form on your website, click the share icon then \u201cEmbed this form\u201d to reveal an embed code. Reset form A new \u201cReset\u201d button is now available on published forms so that form takers can reset a form back to its initial state.","title":"Improvements to Grist Forms"},{"location":"newsletters/2024-03/#imports-and-exports-two-new-file-formats","text":"DOO Separated Values (DSV) Can your spreadsheet DOO this? The DOO Separated Value (DSV) format is an important improvement to Comma Separated Values (CSV), and is now supported for both importing and exporting by Grist. DSV uses a special character from the Unicode astral plane as a separator, to better protect the integrity of your data. Learn how. Credits: DSV was initially proposed by Jamie Matthews . We\u2019ve followed his specification precisely, but changed the file extension to avoid a conflict with Pipe Separated Values (PSV). The value of this specific character from the Unicode astral plane for testing software was first proposed by Mathias Bynens . Tab Separated Values (TSV) You can now export and import data as Tab Separated Values (TSV) from the Share menu.","title":"Imports and exports - two new file formats!"},{"location":"newsletters/2024-03/#grist-boot-page","text":"An early version of a diagnostics page for self-hosted Grist is now available. Learn how to enable it.","title":"Grist boot page"},{"location":"newsletters/2024-03/#migrate-from-spreadsheetcom","text":"We\u2019ve built a tool to help you migrate your Spreadsheet.com data into Grist, including attachments and relations, without the need to manually download and upload your files. Check it out here !","title":"Migrate from Spreadsheet.com"},{"location":"newsletters/2024-03/#community-highlights","text":"@tomnitschke built some new custom widgets, including An auto action widget that lets you automatically run user actions on page load A widget to view PDF and ODT/ODF files directly in the browser A widget to create DOCX or PDF files from HTML or Markdown You can find them all on Github. @jonathanperret built a custom widget that embeds paysage - a collaborative, visual coding playground - into a Grist document. Um, it\u2019s amazing? Watch the full video here .","title":"Community highlights"},{"location":"newsletters/2024-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-03/#webinar-ai-formula-assistant-best-practices","text":"Grist\u2019s AI Formula Assistant simplifies the hardest part of spreadsheets \u2014 formulas. In April\u2019s webinar we\u2019ll share tips on how to get the most out of the assistant. Thursday April 25 at 3:00pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: AI Formula Assistant Best Practices"},{"location":"newsletters/2024-03/#controlling-spreadsheet-chaos","text":"In March, we saw how Grist can contain spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we look at how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. WATCH MARCH\u2019S RECORDING","title":"Controlling spreadsheet chaos"},{"location":"newsletters/2024-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist is hiring! # Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer What\u2019s New # This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40 Misc. improvements # \ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped Community highlights # FOSDEM lighting talk \u26a1\ufe0f # Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference. Tree visualizer widget \ud83c\udf32 # The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out! DOCX report printing \ud83d\udcc4 # Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub . Signature widget \u270d\ufe0f # Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun. Dynamic reference drop-downs in Grist \ud83d\udd0e # Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ). Simple menu navigation with hyperlinks \ud83d\ude80 # Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Controlling spreadsheet chaos # In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Forms! # In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/02"},{"location":"newsletters/2024-02/#february-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2024 Newsletter"},{"location":"newsletters/2024-02/#grist-is-hiring","text":"Come work with us and help make Grist even better. Note: both applications involve puzzles. \ud83e\udee1 Content Writer Systems Engineer","title":"Grist is hiring!"},{"location":"newsletters/2024-02/#whats-new","text":"This month we\u2019ve seen a huge increase in community activity, which is incredibly exciting. We can\u2019t say enough about how motivating it is to see users on Discord or the Community Forum create cool and useful things in Grist. But we can\u2019t let you have all the fun \u2013 we\u2019ve been very busy behind the scenes. Here are a few things that shipped last month, but keep your eyes open for more in the near future. \ud83d\udc40","title":"What’s New"},{"location":"newsletters/2024-02/#misc-improvements","text":"\ud83d\uddd3\ufe0f Calendar events now open in record cards on double-click \ud83d\uddbc\ufe0f The image viewer custom widget can now display a carousel if multiple URLs are detected \ud83e\udee8 The Markdown widget saves by default when you lose focus \ud83e\udd16 Grist\u2019s AI Formula Assistant can now handle larger documents \u270b Custom widgets now show a nicer warning when required columns aren\u2019t mapped","title":"Misc. improvements"},{"location":"newsletters/2024-02/#community-highlights","text":"","title":"Community highlights"},{"location":"newsletters/2024-02/#fosdem-lighting-talk","text":"Grist contributor and ANCT member Florent gave a great lightning talk at this year\u2019s FOSDEM conference.","title":"FOSDEM lighting talk \u26a1\ufe0f"},{"location":"newsletters/2024-02/#tree-visualizer-widget","text":"The inimitable jperon (who will be showing up again) shared a very neat widget to visualize hierarchical data (like an org chart, for example). It even has bidirectional cursor linking! Check it out!","title":"Tree visualizer widget \ud83c\udf32"},{"location":"newsletters/2024-02/#docx-report-printing","text":"Using docxtemplater , TomNit has shared an updated widget that lets Grist process templated .docx files, essentially allowing you to drive document generation using data in Grist. \ud83e\udd2f Check out the full source code on GitHub .","title":"DOCX report printing \ud83d\udcc4"},{"location":"newsletters/2024-02/#signature-widget","text":"Thanks once again to jperon (and of course the other open-source JS devs who make these experiments possible), we saw a proof-of-concept signature collection widget that is too much fun.","title":"Signature widget \u270d\ufe0f"},{"location":"newsletters/2024-02/#dynamic-reference-drop-downs-in-grist","text":"Rogerio_Penna penned a detailed walkthrough on the Community Forum describing his solution to having filtered/dynamic drop-downs. Have you ever wanted a reference column\u2019s options to be filtered by another reference column? It\u2019s not an uncommon use-case, and Rogerio found a way to do it with the action button custom widget (which the community has also been working on ).","title":"Dynamic reference drop-downs in Grist \ud83d\udd0e"},{"location":"newsletters/2024-02/#simple-menu-navigation-with-hyperlinks","text":"Peter_P_Breithaupt, partially inspired by Grist\u2019s powerful SELF_HYPERLINK() function , created an in-depth exploration of the benefits of using navigation hyperlinks on large flat tables. Most importantly, how you can keep the same active record when switching between pages! Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Simple menu navigation with hyperlinks \ud83d\ude80"},{"location":"newsletters/2024-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-02/#webinar-controlling-spreadsheet-chaos","text":"In March, we\u2019ll see how Grist can contain the spreadsheet sprawl. Similar to our previous Grist/Excel webinar , we\u2019ll see how Grist is designed to avoid many of the very familiar problems associated with spreadsheet workflows, such as merging and normalizing. Thursday March 21 at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Controlling spreadsheet chaos"},{"location":"newsletters/2024-02/#forms","text":"In February, we celebrated our love of forms ! We learned how to create a beautiful form and collect data that will populate a Grist data table upon submission. WATCH FEBRUARY\u2019S RECORDING","title":"Forms!"},{"location":"newsletters/2024-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f AlternativeTo Capterra G2 TrustRadius","title":"Help spread the word"},{"location":"newsletters/2024-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2024-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2024 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Happy new year! # If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09 What\u2019s New # Grist Forms # LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them! API Console # Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session. Community Highlights # Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Forms # February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2024/01"},{"location":"newsletters/2024-01/#january-2024-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2024 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2024 Newsletter"},{"location":"newsletters/2024-01/#happy-new-year","text":"If you haven\u2019t already, remember to check out our 2023 Year in Review . We\u2019re extremely excited for 2024 and are kicking things off in top form. \ud83d\ude09","title":"Happy new year!"},{"location":"newsletters/2024-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2024-01/#grist-forms","text":"LEARN MORE Grist Forms are now live! Build simple and effective forms right inside a Grist document and share with a click. Since the form fields come straight from Grist columns, all your data is piped directly into the correct structure, organized and ready for analysis. Design forms visually within Grist Preview, publish & share with a single click Analyze data in real time But we\u2019re not done here \u2013 we\u2019re continuing to add features to Grist Forms. We want the best data tool to have the easiest way of collecting data. We\u2019re also collecting feedback \u2013 either by emailing us at support@getgrist.com or by dropping us a line in our official Discord server . Visit our Grist Forms page for more information, and dive into our Help Center for complete documentation. For a hands-on look at forms, sign up for this month\u2019s webinar to learn all about them!","title":"Grist Forms"},{"location":"newsletters/2024-01/#api-console","text":"Grist already has extensive reference API documentation , but we\u2019re excited to launch a new API console page to allow developers to test API calls in a handy web UI. Note: This page uses your current session.","title":"API Console"},{"location":"newsletters/2024-01/#community-highlights","text":"Do you destruct/construct URLs often? Andreas Kloeckner ( @inducer on GitHub) has shared a small custom widget to allow proper (clickable) viewing of computed URLs. Hot on the heels of gorist , @hooksie1 has shared an alpha version of vrist , a Grist client in V ! What\u2019s next, TypeGrist? Grust? Can Grist kanban? Grist fans band together and plan, and Grist can kanban! Users on our Discord server started brainstorming, leading to several prototypes of a Grist kanban solution: one that doesn\u2019t use any custom widgets (!) by wunter8 and a full-blown draggable custom widget by @jperon Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2024-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2024-01/#webinar-forms","text":"February is for celebrating our love \ud83d\udc96\u2026of forms! We\u2019ll learn how to create a beautiful form view to collect data that will populate a Grist data table upon submission. Thursday February 22 at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Forms"},{"location":"newsletters/2024-01/#markdown-widget-magic","text":"In January, we dove into our Proposals & Contracts example and learned how to use the Markdown widget to create printable PDF files with data from your Grist document. WATCH JANUARY\u2019S RECORDING","title":"Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2024-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2024-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW What\u2019s New # Coming (very) soon: Forms # Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD Beta Testing: Grist on AWS Marketplace # In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks! Other improvements # Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card. Community Highlights # On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f # In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Multimedia Views # In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/12"},{"location":"newsletters/2023-12/#december-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. We\u2019ve published an overview of all that\u2019s gone on during a very busy 2023 for Grist. Check out our Year in Review for the full scoop, as well as an abundance of amazing community contributions and a sneak peek at what\u2019s to come in 2024. \ud83e\udde0 READ THE YEAR IN REVIEW","title":"December 2023 Newsletter"},{"location":"newsletters/2023-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-12/#coming-very-soon-forms","text":"Screenshot of the form editor\u2019s design. Forms are almost here, and we couldn\u2019t be more excited. We\u2019ll release a preview version for testing in January, and would love early feedback from users. To provide feedback, join our Discord server. JOIN DISCORD","title":"Coming (very) soon: Forms"},{"location":"newsletters/2023-12/#beta-testing-grist-on-aws-marketplace","text":"In 2024, we plan to release new options for self-hosted Grist, so that it becomes easier to run (and maintain) on your own infrastructure. Self-hosted Grist can already run almost anywhere, especially when someone has done the work of packaging it for your specific environment. One of the first examples of this was Adrian\u2019s Grist package for Unraid , and one of the most recent is Florent\u2019s Grist package for YunoHost from just last month. At Grist Labs, we have started work on a Grist package for the AWS Marketplace, and are seeking friendly users on the AWS cloud to try it out and give feedback. Interested? Please join our Discord and watch for an announcement in January, and thanks!","title":"Beta Testing: Grist on AWS Marketplace"},{"location":"newsletters/2023-12/#other-improvements","text":"Advanced Charts and the JupyterLite Notebook are now official custom widgets available in the drop-down. In table widgets, you can now hit the spacebar to open the selected row as a record card.","title":"Other improvements"},{"location":"newsletters/2023-12/#community-highlights","text":"On the Grist Discord , @CoverWhale shared their early-stage Grist Go client, with the delightful name Gorist . Also on the Discord, @jperon shared a pug_py widget to help develop custom widgets. Translation started for Romanian (thanks @chiuta)! See here for more information on translating Grist. We are so impressed with @jperon\u2019s diligence in solving as many Advent of Code puzzles in Grist as possible. Hero status. Just\u2026just look at this: https://docs.getgrist.com/cU5y9f2ViVQQ/AOC2023/p/17 Meanwhile at Grist\u2026 Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-12/#webinar-markdown-widget-magic","text":"In January, we\u2019ll learn how to use the Markdown widget to create printable PDF files, populated with data from your Grist document. We\u2019ll rebuild our \ud83d\udcdd Proposals & Contracts Template to show you how it\u2019s done! Thursday January 18 at 4:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Markdown Widget Magic \ud83e\uddd9\u200d\u2642\ufe0f"},{"location":"newsletters/2023-12/#multimedia-views","text":"In December, we covered multimedia views and explored even more ways to display your data. Maps and Notepads and Video Players, oh my! WATCH DECEMBER\u2019S RECORDING","title":"Multimedia Views"},{"location":"newsletters/2023-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-12/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Hang out with us on Discord! # We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD Record cards # Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page . Add column with type # Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold! Security update for self-hosters # We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details. Grist Console Q&A # CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.) Community Highlights # Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Multimedia Views # In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Charts and Summary Tables # In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/11"},{"location":"newsletters/2023-11/#november-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2023 Newsletter"},{"location":"newsletters/2023-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-11/#hang-out-with-us-on-discord","text":"We\u2019ve created an official Grist Discord server for those interested in a place to chat about Grist and Grist-related topics. As a fun kickoff, we\u2019re going to see how many of this year\u2019s Advent of Code puzzles we can solve with Grist. \ud83d\udcc6\ud83e\udde9 JOIN DISCORD","title":"Hang out with us on Discord!"},{"location":"newsletters/2023-11/#record-cards","text":"Don\u2019t let the humble name fool you, this is a big one! We all know how helpful references are. But sometimes you want to take a peek at the referenced record without changing your current view. Now you can! The \u201clink\u201d icon in reference and reference list columns is now an actual hyperlink, and it opens the referenced record in a pop-up. But this doesn\u2019t just affect references! You can also view any row in a table as a card with a single click, letting you see its base record in full. Record cards are default card views for every table. They can be configured on the Raw Data page .","title":"Record cards"},{"location":"newsletters/2023-11/#add-column-with-type","text":"Our quest to improve the experience of adding new columns continues. Now you can set the column type right in the \u201cAdd Column\u201d menu. Behold!","title":"Add column with type"},{"location":"newsletters/2023-11/#security-update-for-self-hosters","text":"We released a security fix earlier this month related to the suggested configuration of grist-core with traefik, as well as for grist-omnibus . See this Community update for more details. The fix also affects those who do Grist authentication by forwarding a header for all endpoints (e.g. behind HTTP Basic Auth), as it makes a previously optional flag required. You may need to review your settings \u2013 see this issue for details.","title":"Security update for self-hosters"},{"location":"newsletters/2023-11/#grist-console-qa","text":"CTO Paul was able to share some fascinating Grist-related nuggets in an excellent Console Q&A . Learn about the origins of the name \u201cGrist\u201d, how (and why) we build Grist, and how we may be able to communicate with extraterrestrials. \ud83d\udc7d (That last one may not be directly related to Grist.)","title":"Grist Console Q&A"},{"location":"newsletters/2023-11/#community-highlights","text":"Self-hosters: Grist now natively supports authentication with OpenID Connect. See the complete documentation here . A huge thanks to @fflorent from ANCT for their work on this PR! Looking for a simpler way to self-host Grist? Florent has also packaged Grist on YunoHost , a Debian-based OS designed to help democratize self-hosting. Check out Florent\u2019s post in our forum for more information. Andreas Kl\u00f6ckner ( @inducer on GitHub) has shared not one, not two, but THREE Grist-related projects on the Community forum : grist-availability : a tool to help with multi-person scheduling using the new calendar widget. grist-mailmerge : a batch emailer driven by a Grist table and a Jinja template. pygrist-mini : a tiny Grist API client for Python. Translations started for Arabic, Czech, Chinese (Traditional), Dutch and Thai! We\u2019re now at 20 languages either complete or in-progress, all thanks to community contributors. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-11/#webinar-multimedia-views","text":"In December, we\u2019ll cover multimedia views so you can explore even more ways to display your data. Maps and Notepads and Video Players, oh my! Thursday December 14 at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Multimedia Views"},{"location":"newsletters/2023-11/#charts-and-summary-tables","text":"In November, we learned how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. WATCH NOVEMBER\u2019S RECORDING","title":"Charts and Summary Tables"},{"location":"newsletters/2023-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-11/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons! What\u2019s New # Formula shortcuts # If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation . Beta feature: Advanced Chart custom widget # The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration! Beta feature: JupyterLite notebook widget # This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs . Colorful events in the calendar widget! # You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8 Bidirectional cursor linking # Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action: Grist CSV Viewer file downloads # You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files. Grist Labs at NEC 2023 # Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch ! Even more improvements! # A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT . Community Highlights # @jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Charts and Summary Tables # In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Calendars and Cards # In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING Templates # We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/10"},{"location":"newsletters/2023-10/#october-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter New formula shortcuts There\u2019s a new menu of options when adding a column, including lookup and formula shortcuts! Beta: Advanced Chart custom widget Try out an experimental widget that gives Grist extended charting capabilities. Beta: JupyterLite notebook widget Run custom Python code in our experimental JupyterLite widget. Colorful calendar events \ud83c\udf08 You can now color events, and increased performance for large numbers of events. Grist CSV Viewer file downloads Users can now download CSV and XLSX files right from the Grist CSV Viewer. Grist at NEC 2023 \ud83c\uddeb\ud83c\uddf7 A quick update from our trip to Bordeaux, talking about Grist and the digital commons!","title":"October 2023 Newsletter"},{"location":"newsletters/2023-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-10/#formula-shortcuts","text":"If you open the \u201cAdd Column\u201d menu, you\u2019ll see a few new options that weren\u2019t there before: Lookups and Shortcuts . These are two types of one-click formula creation that help build out your Grist documents with ease. Lookups: If your table references other tables, or is referenced in other tables, you\u2019ll be able to access related data with a single click, and perform operations such as sum or average where applicable. Shortcuts: Shortcuts introduce a one-click way to add common trigger formulas: timestamps, authorship stamps, detecting duplicates, and creating unique identifiers. Learn more in our documentation .","title":"Formula shortcuts"},{"location":"newsletters/2023-10/#beta-feature-advanced-chart-custom-widget","text":"The Advanced Chart custom widget gives you more power and flexibility than Grist\u2019s built-in charts, offering a wide variety of chart types as well as increased control over styling and layout. To learn more and try it out yourself, check out our beta docs , and remember: regularly click the \u201cSave\u201d button above the widget to keep your configuration!","title":"Beta feature: Advanced Chart custom widget"},{"location":"newsletters/2023-10/#beta-feature-jupyterlite-notebook-widget","text":"This experimental widget lets you run custom Python code in JupyterLite , a version of JupyterLab running entirely in the browser. It can use the custom widget plugin API and access or modify any data in the document (subject to Access Rules), unlocking nearly unlimited possibilities for advanced users. For more information on how to get it up and running, check out our beta docs .","title":"Beta feature: JupyterLite notebook widget"},{"location":"newsletters/2023-10/#colorful-events-in-the-calendar-widget","text":"You can now color-code events in the calendar widget ! All you need to do is assign an optional \u201cType\u201d column that contains an event\u2019s category and styling. \ud83c\udfa8","title":"Colorful events in the calendar widget!"},{"location":"newsletters/2023-10/#bidirectional-cursor-linking","text":"Thanks to the hard work of @jvorob (who was with us for the summer and will be sorely missed!), cursor linking has received a significant upgrade. As part of this, two widgets can now be linked in both directions, allowing more natural cursor interaction. Bidirectional linking makes the most sense in action:","title":"Bidirectional cursor linking"},{"location":"newsletters/2023-10/#grist-csv-viewer-file-downloads","text":"You can now download files displayed in the Grist CSV Viewer as CSV and XLSX files.","title":"Grist CSV Viewer file downloads"},{"location":"newsletters/2023-10/#grist-labs-at-nec-2023","text":"Some of the Grist team travelled to Bordeaux, France to talk about the no-code aspect of Grist at NEC 2023 , a conference focused on the digital commons . We were able to meet Grist users serving in the public sector, and even some of our invaluable open source contributors from ANCT . If you\u2019re interested in having Grist team members talk at your convention about how cool and useful Grist is, get in touch !","title":"Grist Labs at NEC 2023"},{"location":"newsletters/2023-10/#even-more-improvements","text":"A stealthy but major milestone for Grist\u2019s backend: it has been updated to Node 18. Open source self-hosters: Grist can now serve metrics to the Prometheus monitoring system via a new environment variable GRIST_PROMCLIENT_PORT .","title":"Even more improvements!"},{"location":"newsletters/2023-10/#community-highlights","text":"@jperon is back at it again with a new custom SQL selector widget that leverages last month\u2019s SQL endpoint . \ud83e\udd2f Thanks to @marumaru for kicking off our Japanese translation! See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-10/#webinar-charts-and-summary-tables","text":"In November, we\u2019ll learn how to summarize and analyze data in charts and summary tables, with tips and tricks to get more out of summary tables. Monday November 20th at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Charts and Summary Tables"},{"location":"newsletters/2023-10/#calendars-and-cards","text":"In October, we looked at the new calendar widget and discovered the how to get the most out of calendar views in your documents. Since card widgets pair nicely with calendars, we looked at those as well. WATCH OCTOBER\u2019S RECORDING","title":"Calendars and Cards"},{"location":"newsletters/2023-10/#templates","text":"We\u2019ve gone through our roster of templates and added in a sprinkling of calendar widgets to make sure they\u2019re as helpful as possible. Take, for example, the Time Tracking + Invoicing template , which now has a calendar view: GO TO TEMPLATE","title":"Templates"},{"location":"newsletters/2023-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-10/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Calendar widget \ud83d\uddd3\ufe0f # The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION SQL endpoint # Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation. Community Highlights # @jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # New orientation video # New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users. Webinar: Calendar # Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Deconstructing the Payroll Template # When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING Templates # Trip Planning # Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE Social Media Content Calendar # But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/09"},{"location":"newsletters/2023-09/#september-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2023 Newsletter"},{"location":"newsletters/2023-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-09/#calendar-widget","text":"The time has come\u2026 the Official Grist Calendar Widget is here! We know its handy as we\u2019ve already updated a bunch of our templates to include it. \ud83d\ude05 We\u2019ve even created new ones that specifically feature the calendar widget (more info below). Events are created using associated Date or DateTime columns Day, week & month views Drag-and-drop event editing Support for cursor linking \u2013 select an event and have it update a linked widget automatically (this is helpful if you want to see an event\u2019s details in a card widget, for example) SEE DOCUMENTATION","title":"Calendar widget \ud83d\uddd3\ufe0f"},{"location":"newsletters/2023-09/#sql-endpoint","text":"Grist now has an SQL endpoint that lets you run read-only SELECT queries via the Grist API! This is exciting for a very \u201cselect\u201d set of Grist users. \ud83e\udd20 For example, here\u2019s a simple SQL query performed on our Lightweight CRM template (these are not real emails!): SELECT Email FROM Contacts WHERE Due IS NOT NULL See here for the complete API documentation.","title":"SQL endpoint"},{"location":"newsletters/2023-09/#community-highlights","text":"@jperon\u2019s found a way to create an advanced search widget using Python\u2019s eval method. See the community post for an example, as it\u2019s a bit tricky to explain. Shout out to @prijatelj.francek for their work on the new Slovenian translation. See here for more information on translating Grist. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-09/#new-orientation-video","text":"New Grist users will see an updated orientation video when they sign up, which may also be a helpful refresher for existing users.","title":"New orientation video"},{"location":"newsletters/2023-09/#webinar-calendar","text":"Calendars are here! Learn all the tips and tricks to get the most out of calendar views in your documents. Cards pair nicely with calendars, so we\u2019ll dive into card widgets, too. Thursday October 26th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Calendar"},{"location":"newsletters/2023-09/#deconstructing-the-payroll-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In September we rebuilt our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. Learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. WATCH SEPTEMBER\u2019S RECORDING","title":"Deconstructing the Payroll Template"},{"location":"newsletters/2023-09/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-09/#trip-planning","text":"Summer may be over, but that just means it\u2019s time to plan your next trip. Use Grist\u2019s new Trip Planning template to plan & organize multiple trips in one place, with detailed itineraries and packing lists. This template is also a great showcase for the new calendar widget! GO TO TEMPLATE","title":"Trip Planning"},{"location":"newsletters/2023-09/#social-media-content-calendar","text":"But wait, there\u2019s more! We\u2019ve adapted and improved the document we use internally to help keep track of our social posting and turned it into a template. Build and track campaigns, approve content and schedule posts (text/image/video). And yes, this template makes great use of the new calendar widget as well! GO TO TEMPLATE","title":"Social Media Content Calendar"},{"location":"newsletters/2023-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-09/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33 Work at Grist # Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description . What\u2019s New # Grist CSV Viewer # Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action AI Assistant \u2013 Support for Llama # Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables . \ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers # You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.) .grist file download options # You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32 File importing redesign # File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping. More Improvements # Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!) Tips & Tricks # Grist for spreadsheet users # New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps. Self-hosting grist-static # The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer. Community Highlights # @jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Payroll Template # In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Deconstructing the Class Enrollment Template # When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING Templates # Proposals & Contracts # Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/08"},{"location":"newsletters/2023-08/#august-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Job Opportunity We\u2019re looking for a Video Creator & Graphic Designer to join our team. Come work with us! Grist CSV Viewer Set free the CSV! This new tool gives your site\u2019s users Grist-powered CSV previewing. AI Assistant Llama Support Run local Llama models with the help of llama2-cpp-python . \ud83e\udd99 .grist File Download Options Reduce filesize by downloading without history, or make templates by downloading only a file\u2019s structure. File Importing Redesign Improved support for importing multiple files/sheets and column mapping. QR Code Custom Widget Generate QR codes in a flash, right within Grist. \ud83e\udd33","title":"August 2023 Newsletter"},{"location":"newsletters/2023-08/#work-at-grist","text":"Grist is currently looking for a Video Creator & Graphic Designer who has a good understanding of spreadsheet-database tools. If you\u2019re skilled at creating engaging videos and visually compelling graphic designs, come work with us! This is a contract position. For more details, check out the job description .","title":"Work at Grist"},{"location":"newsletters/2023-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-08/#grist-csv-viewer","text":"Another month, another exciting update \u2013 this time for the venerable file format we all know and love, the .csv . The Grist CSV Viewer is a free tool that gives CSVs the power of Grist on the web. No more downloading and opening CSVs in an external app, now you can preview and interact with CSVs in a Grist-powered spreadsheet! The viewer is open source, supports features like filtering, sorting, copy/pasting, and even formulas. And it\u2019s easy to install, with just two lines of HTML added to any website. See it in action","title":"Grist CSV Viewer"},{"location":"newsletters/2023-08/#ai-assistant-support-for-llama","text":"Grist self-hosters can now use the Llama family of self-hostable models via the llama2-cpp-python wrapper by configuring the expanded AI Assistant-related environment variables .","title":"AI Assistant \u2013 Support for Llama"},{"location":"newsletters/2023-08/#styled-column-headers","text":"You can now style column headers! Look! (Thanks to @CamilleLegeron and the ANCT for the PR.)","title":"\ud835\udce2\ud835\udcfd\ud835\udd02\ud835\udcf5\ud835\udcee\ud835\udced column headers"},{"location":"newsletters/2023-08/#grist-file-download-options","text":"You can now download a .grist file\u2019s structure without any data, which is useful for templating. Or you can download a .grist without document history which reduces file size. In our tests, we\u2019ve seen documents go from 10.8mb to 1.5mb after omitting file history. \ud83d\ude32","title":".grist file download options"},{"location":"newsletters/2023-08/#file-importing-redesign","text":"File importing has been updated with a new flow, with better support for multiple files/sheets and column mapping.","title":"File importing redesign"},{"location":"newsletters/2023-08/#more-improvements","text":"Grist has been upgraded from Python 3.9 to 3.11. This update brings with it the following new features: Execution should be slightly faster. Additional support for the statistics module , including covariance , correlation , and linear_regression functions. Support for Structural Pattern Matching . You can now see ^ marks pointing at the problem in tracebacks, which can help debugging errors. Links are now clickable in widget and column description tooltips. Grid selection now supports CTRL/CMD+Shift+ shortcuts. If you\u2019ve used this shortcut in other popular spreadsheet products, you\u2019ll be right at home! (Thanks to @Ocarthon for contributing to this!)","title":"More Improvements"},{"location":"newsletters/2023-08/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-08/#grist-for-spreadsheet-users","text":"New to Grist but an expert with Excel/Google Sheets? Our new blog post covers what makes Grist different and why it\u2019s designed that way, specifically for users familiar with the usual spreadsheet apps.","title":"Grist for spreadsheet users"},{"location":"newsletters/2023-08/#self-hosting-grist-static","text":"The grist-static GitHub repo has been updated with extra information to help you serve Grist on your own website or CDN \u2013 including the new CSV Viewer.","title":"Self-hosting grist-static"},{"location":"newsletters/2023-08/#community-highlights","text":"@jperon\u2019s nifty QR code widget from back in January has been officially added as a custom widget! \ud83c\udf89 Scan the code above to visit the original post, and click here to learn more about Custom widgets. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-08/#webinar-deconstructing-the-payroll-template","text":"In September we\u2019ll rebuild our Payroll Template . This template uses formulas to look up hourly rates based on person, role, and date. You\u2019ll also learn how to build dynamic summary dashboards that summarize data by categories and let you drill into the records in those categories. Thursday September 21st at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Deconstructing the Payroll Template"},{"location":"newsletters/2023-08/#deconstructing-the-class-enrollment-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In August we rebuilt our Class Enrollment template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. WATCH AUGUST\u2019S RECORDING","title":"Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-08/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-08/#proposals-contracts","text":"Natalie has created a detailed tutorial showing how this (very advanced) template was created. We actually use a version of this very template at Grist to handle contracts, so it\u2019s tried and tested! Not only can this template be used to generate printable contracts with dynamic content, it can also generate contract templates themselves . So you can have several versions of dynamic contracts created from the same Grist document. \ud83e\udd2f GO TO TEMPLATE","title":"Proposals & Contracts"},{"location":"newsletters/2023-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-08/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist. What\u2019s New # AI Formula Assistant # A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center . Floating formula editor # Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save. \ud83e\udd29 Better handling of emojis on Page names # At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly. Telemetry for self-hosted users # We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time. Tips & Tricks # Access Rules: Restrict creation of new record until all mandatory fields are filled in # In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation! Community Highlights # @enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Class Enrollment Template # In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR Deconstructing the Digital Sales CRM Template # When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING Templates # Budgeting # This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE Help spread the word # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/07"},{"location":"newsletters/2023-07/#july-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. July was a big month for Grist, with a brand-new feature that brings a suite of other improvements. As always, we\u2019d love to hear feedback and welcome any examples of cool/useful things you create with Grist.","title":"July 2023 Newsletter"},{"location":"newsletters/2023-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-07/#ai-formula-assistant","text":"A while ago we did an experiment with AI to see how it could help Grist users with formulas. We\u2019re excited to announce the experiment is now a feature in an integrated AI Formula Assistant ! TLDR: Using plain language, chat with an AI to help create Grist formulas. Grist sends the necessary context, so no need to describe your data\u2019s structure. Because the AI Assistant makes use of Grist\u2019s structure, we strongly recommend reading this quick list of best practices . All Grist accounts get 100 credits for free \u2013 so you can test out the assistant right now! Check out the full announcement . Learn how to use the assistant and how data is handled in our help center .","title":"AI Formula Assistant"},{"location":"newsletters/2023-07/#floating-formula-editor","text":"Working on complex formulas and feeling a bit cramped? No longer! The AI Formula Assistant brings with it a new floating formula editor. Expand, collapse, and drag the editor around the screen as you please. Just start editing any formula column and hit the expand icon at the top right of the cell. Changes made in the expanded floating editor won\u2019t apply until you hit the Save button. Bonus feature: Preview changes! Click the new Preview button in the floating editor to see how your changes will affect the column in question. Easily test formulas without updating your entire dataset. Your changes won\u2019t apply until you hit Save.","title":"Floating formula editor"},{"location":"newsletters/2023-07/#better-handling-of-emojis-on-page-names","text":"At Grist we love using emojis as visual shorthand for Page names. They\u2019re easily recognizable, and work well when the Pages panel is minimized. Now, Grist automatically detects when an emoji is used at the start of a name and styles it accordingly.","title":"\ud83e\udd29 Better handling of emojis on Page names"},{"location":"newsletters/2023-07/#telemetry-for-self-hosted-users","text":"We rely on product usage data to help make improvements to Grist. Self-hosters can now opt-in to sharing limited telemetry data with us. We only collect usage statistics, never document contents. If you\u2019re a fan of Grist who self-hosts, opting into telemetry is one of the best ways to support us. You can read a detailed list of what we collect in our help center . To opt in, click the opt-in button on the \u201cSupport Grist\u201d banner on your homepage, or modify the GRIST_TELEMETRY_LEVEL environment variable as described in our help center\u2019s telemetry overview . You may opt-out again at any time.","title":"Telemetry for self-hosted users"},{"location":"newsletters/2023-07/#tips-tricks","text":"","title":"Tips & Tricks"},{"location":"newsletters/2023-07/#access-rules-restrict-creation-of-new-record-until-all-mandatory-fields-are-filled-in","text":"In the Showcase forum, Natalie takes us through how to use Access Rules to restrict a user from creating new records until all mandatory fields are filled in for existing records in a table. Very handy for data sanitation!","title":"Access Rules: Restrict creation of new record until all mandatory fields are filled in"},{"location":"newsletters/2023-07/#community-highlights","text":"@enthus1ast built a small daemon (\ud83d\ude08) as a proof-of-concept infrastructure monitoring system using Grist. As a bonus, they also provided a REST API client written in Nim. @Leonard_Gallion shared some handy Python code that lets you download documents as .grist files . Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-07/#webinar-deconstructing-the-class-enrollment-template","text":"In August we\u2019ll rebuild our Class Enrollment Template . This template uses reference columns to relate data in five different tables. Learn when to use reference columns, in which direction, and how to use helper tables to better structure your data. You\u2019ll also learn how to build productive dashboards that view the same data from different perspectives. Thursday August 17th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Deconstructing the Class Enrollment Template"},{"location":"newsletters/2023-07/#deconstructing-the-digital-sales-crm-template","text":"When looking at our templates you may wonder why templates are structured in a particular way. In July we rebuilt our Digital Sales CRM template. This template contains common structural patterns used in Grist documents for variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. WATCH JULY\u2019S RECORDING","title":"Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-07/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-07/#budgeting","text":"This budget template simplifies setting a budget, tracking income and expenses, and comparing expectations with financial reality. \ud83d\udcb8 GO TO TEMPLATE","title":"Budgeting"},{"location":"newsletters/2023-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word"},{"location":"newsletters/2023-07/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Highlighting for selector rows # A small but mighty fix. Grist now highlights the selected row linked to widgets on a page. Community Highlights # @wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum ! Learning Grist # Webinar: Deconstructing the Digital Sales CRM Template # In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR Deconstructing the Software Deals Tracker Template # In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING Templates # Field Trip Planner # Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE Nutrition Tracker # Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE Hurricane Preparedness # Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/06"},{"location":"newsletters/2023-06/#june-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2023 Newsletter"},{"location":"newsletters/2023-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-06/#highlighting-for-selector-rows","text":"A small but mighty fix. Grist now highlights the selected row linked to widgets on a page.","title":"Highlighting for selector rows"},{"location":"newsletters/2023-06/#community-highlights","text":"@wunter8 has created a calendar widget that is a great example of what custom widgets can do. Who knows, maybe this could inspire an official calendar widget? \ud83d\udc40 (@ToJans also shared a calendar widget back in March .) Database-heads will appreciate @John1\u2019s PostgreSQL Foreign Data Wrapper , which is a very neat tool that allows you to import and query Grist tables in PostgreSQL. Working on something cool with Grist? Let us know by posting in the Showcase forum !","title":"Community Highlights"},{"location":"newsletters/2023-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-06/#webinar-deconstructing-the-digital-sales-crm-template","text":"In July we\u2019ll rebuild our Digital Sales CRM template . This template contains common structural patterns used in Grist documents for a variety of use cases. It also has common, simple examples of lookup formulas, and analyzes the same sales data two ways: lifetime revenue by customer and lifetime revenue by product. Thursday July 20th at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR","title":"Webinar: Deconstructing the Digital Sales CRM Template"},{"location":"newsletters/2023-06/#deconstructing-the-software-deals-tracker-template","text":"In June we rebuilt the Software Deals Tracker . This template has multiple pages and widgets focused on different workflows, and yet the document is built on only one data table. We show you why to help you anticipate your own use cases where all you need is one table with multiple views. WATCH JUNE\u2019S RECORDING","title":"Deconstructing the Software Deals Tracker Template"},{"location":"newsletters/2023-06/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-06/#field-trip-planner","text":"Organizing a field trip requires juggling parental permissions, lunches, payments, chaperones, and other faculty. It\u2019s a lot! Bring zen to your field trip planning with this new template. GO TO TEMPLATE","title":"Field Trip Planner"},{"location":"newsletters/2023-06/#nutrition-tracker","text":"Track your nutrition, macros, and calories with this simple template created by user Jawaad Mahmood. \ud83c\udf4f Check out Jawaad\u2019s showcase post in our community . GO TO TEMPLATE","title":"Nutrition Tracker"},{"location":"newsletters/2023-06/#hurricane-preparedness","text":"Hurricane season is a weight on the minds of many. If you\u2019re near a coast, don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2023-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s a short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius AlternativeTo","title":"Help spread the word?"},{"location":"newsletters/2023-06/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcard Contest: Vote for the Best Deck! # In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE What\u2019s New # Column and Widget Descriptions # In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel. Webhooks! # We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site. Learning Grist # Webinar: Deconstructing a Template, Software Deals Tracker # When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR Importing Data # In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING Templates # Expense Tracking for Teams # Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE Simple Time Tracker # Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/05"},{"location":"newsletters/2023-05/#may-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2023 Newsletter"},{"location":"newsletters/2023-05/#flashcard-contest-vote-for-the-best-deck","text":"In May we sponsored a flashcard contest. Grist users were invited to build the best flashcard deck by using our Flashcards Template . Thank you for your submissions! The community will now vote for the best flashcard decks! VOTE","title":"Flashcard Contest: Vote for the Best Deck!"},{"location":"newsletters/2023-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-05/#column-and-widget-descriptions","text":"In March @CamilleLegeron made it possible to add descriptions to columns. Now, columns with descriptions have an info icon in their headers, which when hovered over or clicked reveal the respective description. This feature has been enabled on widgets as well, providing even more context to your team. To set a column or widget description, click into the title to open the renaming dialog which now includes a description field. Alternatively, descriptions may be set in the creator panel.","title":"Column and Widget Descriptions"},{"location":"newsletters/2023-05/#webhooks","text":"We\u2019re happy to release beta webhooks support! \ud83c\udf89 Webhooks are one way to send information from Grist to other apps. For example, if a record is updated in Grist, send a message in Slack. When in a document, navigate to the Settings page listed the left-side panel under \u201cTools\u201d. The \u201cManage Webhooks\u201d button at the bottom will take you to the webhooks page. Unofficial API support for webhooks has been available for some time, forming the basis for Zapier, Pabbly Connect, and other integrations. That API will continue to work, and is no longer limited to specific partners \u2014 you can now use it with any site.","title":"Webhooks!"},{"location":"newsletters/2023-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-05/#webinar-deconstructing-a-template-software-deals-tracker","text":"When looking at our templates you may wonder why templates are structured in a particular way. Over the next four months we\u2019ll rebuild templates with you and highlight common patterns in each document. In June, we\u2019ll show you how we built the Software Deals Tracker . The template has three pages, each with multiple widgets focused on different workflows, and yet the document is built on only one data table. We\u2019ll show you why to help you anticipate your own use cases where all you need is one table with multiple views. Thursday June 22nd at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR","title":"Webinar: Deconstructing a Template, Software Deals Tracker"},{"location":"newsletters/2023-05/#importing-data","text":"In May, we explored all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. WATCH MAY\u2019S RECORDING","title":"Importing Data"},{"location":"newsletters/2023-05/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-05/#expense-tracking-for-teams","text":"Manage all employee expenses in one place. No more wrangling employees\u2019 expense spreadsheets into a master list every month. With access rules, employees can log into Grist, view and update only their expenses, and Grist takes care of the rest. Watch this template\u2019s webinar to learn more about how this template was built. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2023-05/#simple-time-tracker","text":"Like a stopwatch in a spreadsheet to create a log of time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2023-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-05/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Flashcards Contest: Build the Best Knowledge Deck # In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE What\u2019s New # We rickrolled, and so can you # Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document! Grist-static: Publish data on static sites without embeds # Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design. Another werewolf strike: MOONPHASE() # Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon. Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE Learning Grist # Webinar: Importing Data # Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR Trigger Formulas # In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING New Template # Test Prep # Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/04"},{"location":"newsletters/2023-04/#april-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2023 Newsletter"},{"location":"newsletters/2023-04/#flashcards-contest-build-the-best-knowledge-deck","text":"In May we\u2019re sponsoring a flashcards contest. Build the best flashcard deck by using our Flashcards Template to share your expertise on any topic. The community will then vote for the top three most mind-blowing flashcard decks. Each of the three winners will receive a rare and highly sought-after Grist thermos . LEARN MORE","title":"Flashcards Contest: Build the Best Knowledge Deck"},{"location":"newsletters/2023-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-04/#we-rickrolled-and-so-can-you","text":"Last month we invited you to answer a Grist survey and rewarded your kindness with an April Fool\u2019s joke: a rickroll in Grist. The great news is that you can pay this forward whenever you like to whomever you like with any document. Take any Grist anchor link and replace the r in the URL with rr. We call it \u201crickrow\u201d because that r anchors to a row. To find an anchor link, right click on any cell and select \u201ccopy anchor link\u201d from the context menu (keyboard shortcut shown in menu). In this case the anchor link is https://templates.getgrist.com/doc/lightweight-crm#a1.s1.r20.c2 To rickrow someone, edit the anchor link to https://templates.getgrist.com/doc/lightweight-crm#a1.s1.rr20.c2 For full rickrolling, be sure the recipient can access the document!","title":"We rickrolled, and so can you"},{"location":"newsletters/2023-04/#grist-static-publish-data-on-static-sites-without-embeds","text":"Take a peek at our CTO Paul\u2019s latest experiment. Grist-static offers a way to view and interact with .grist files on regular websites, with no special back-end support needed. Grist-static is great for displaying spreadsheet reports on a website, similar to PDF reports, but better! With grist-static, viewers will be able to change selections, and experiment with changing numbers to see what happens. Every viewer has their own copy, and their changes won\u2019t be seen by others, or stored. This would also be a scalable way to show a Grist document to millions of simultaneous users. See some examples here . Learn more on Github . We want to hear from you. Do you already see a use case for grist-static? Are you excited by it? Tell us by emailing us at support@getgrist.com , or posting in our community forum . This is just the beginning for grist-static and your input helps guide our design.","title":"Grist-static: Publish data on static sites without embeds"},{"location":"newsletters/2023-04/#another-werewolf-strike-moonphase","text":"Another function contribution by user @were_functions on Twitter. The MOONPHASE() function returns the phase of the moon on the given date. Here\u2019s what it tells us about the date of this newsletter: MOONPHASE(TODAY()) is \ud83c\udf13. With other arguments, we can find that it\u2019s been 8 days since new moon, or 0.27 of a month. Also that MOONPHASE(TODAY(), \"lunacy\") is \ud83d\udd7a. Safe for now, but it\u2019s less than a week to the next full moon. Someone ought to start taking the wolfsbane potion. Try it yourself! Tip: Replace \u201clunacy\u201d with \u201cdays\u201d to calculate the age of the moon in days, or \u201cfraction\u201d to calculate the fraction of the lunar month since the new moon.","title":"Another werewolf strike: MOONPHASE()"},{"location":"newsletters/2023-04/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 SPONSOR GRIST-CORE","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-04/#webinar-importing-data","text":"Many Grist users have imported spreadsheets into Grist, but not all are aware of all the cool import features that Grist offers, such as incremental imports, merge fields, and formula transformations. In May, we\u2019ll dig into importing data like a pro. Tuesday May 16th at 12:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR","title":"Webinar: Importing Data"},{"location":"newsletters/2023-04/#trigger-formulas","text":"In April, we learned all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. WATCH APRIL\u2019S RECORDING","title":"Trigger Formulas"},{"location":"newsletters/2023-04/#new-template","text":"","title":"New Template"},{"location":"newsletters/2023-04/#test-prep","text":"Create timed sample tests and flashcards to ace your next test! GO TO TEMPLATE","title":"Test Prep"},{"location":"newsletters/2023-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-04/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist. The Big Grist Survey! \ud83d\udd25 # Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY Want to work at Grist? # Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ . What\u2019s New # Minimizing Widgets # Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page. Grist Basics In-Product Tutorial # Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards. Open Source Contributions # Column Descriptions # Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel. Custom Widget Calendar View # @ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa TASTEME() ?? # Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved? Update on the Grist Electron App \u2014 Sandboxing! # Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list . Sponsor Grist on Github # Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST Learning Grist # Webinar: Trigger Formulas # Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR Data Cleaning # In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING New Template and Custom Widget # Flashcards # Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/03"},{"location":"newsletters/2023-03/#march-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Minimizing Widgets Make room on your dashboards by collapsing widgets into buttons. Grist Basics Tutorial An in-product, hands-on way to learn Grist. User Poll Let us know what you think! \ud83d\udc40 Open Source Contributions \ud83d\ude4f Column descriptions, calendar custom widget, and more! Flashcards Custom Widget A new custom widget perfect for learning. Work at Grist! Two marketing positions are now open at Grist.","title":"March 2023 Newsletter"},{"location":"newsletters/2023-03/#the-big-grist-survey","text":"Help improve Grist by taking our 5-question user experience poll! There may even be a prize! \ud83c\udf08\ud83d\udcb0 TAKE SURVEY","title":"The Big Grist Survey! \ud83d\udd25"},{"location":"newsletters/2023-03/#want-to-work-at-grist","text":"Do you love Grist and love marketing? Check out our new job openings at https://www.getgrist.com/jobs/ .","title":"Want to work at Grist?"},{"location":"newsletters/2023-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-03/#minimizing-widgets","text":"Need more room on your dashboard? Have a widget that doesn\u2019t need to be in view at all times? You can now collapse widgets into buttons at the top of the page! The widget will expand on click and will retain dynamic linking with other widgets in the page.","title":"Minimizing Widgets"},{"location":"newsletters/2023-03/#grist-basics-in-product-tutorial","text":"Learning Grist has never been easier! Check out our new hands-on tutorial. This step-by-step guide will give you the basics to go from spreadsheets to productive dashboards.","title":"Grist Basics In-Product Tutorial"},{"location":"newsletters/2023-03/#open-source-contributions","text":"","title":"Open Source Contributions"},{"location":"newsletters/2023-03/#column-descriptions","text":"Thank you to @CamilleLegeron who made it possible to add descriptions to columns. \ud83c\udf89\ud83d\ude4f You may now set column descriptions in the creator panel.","title":"Column Descriptions"},{"location":"newsletters/2023-03/#custom-widget-calendar-view","text":"@ToJans on Twitter shared his calendar custom widget, as well as a link you can incorporate into your own docs. Check out his tweet . It works! \ud83d\udcaa","title":"Custom Widget Calendar View"},{"location":"newsletters/2023-03/#tasteme","text":"Last month we also received a new function from @DysfunctionalWerewolf. The TASTEME() function returns True for tasty text, False for not tasty text. It\u2019s\u2026useful? \ud83e\udd14 @DysfunctionalWerewolf showed off the function in action on Twitter @were_functions . We had fun trying to crack the pattern for what is and isn\u2019t tasty! Let us know if your data is, uh, werewolf approved?","title":"TASTEME() ??"},{"location":"newsletters/2023-03/#update-on-the-grist-electron-app-sandboxing","text":"Good news, everyone! The Grist electron app now has sandboxing by default. Anyone can download the app without having to compile from source or know Docker. Finally, a super easy way to run Grist locally on your computer. Learn more on Github . Find the right download file in this list .","title":"Update on the Grist Electron App \u2014 Sandboxing!"},{"location":"newsletters/2023-03/#sponsor-grist-on-github","text":"Want to show your support for our open source project? Post about us on all your socials and tell your friends! If you want to do even more, consider sponsoring us on Github. \ud83e\udde1 We\u2019re grateful to our first corporate sponsor Dotphoton, and to sponsor @emanuelegissi! \ud83d\ude4f SPONSOR GRIST","title":"Sponsor Grist on Github"},{"location":"newsletters/2023-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-03/#webinar-trigger-formulas","text":"Grist formulas can be set to trigger on specific record events. Learn all about trigger formulas, when to use them, and common uses of trigger formulas to keep your data organized and productive. Wednesday April 12th at 3:00 pm US Eastern Time. SIGN UP FOR APRIL\u2019S WEBINAR","title":"Webinar: Trigger Formulas"},{"location":"newsletters/2023-03/#data-cleaning","text":"In March, Natalie shared tips for cleaning your data fast in Grist, including a few Grist-only tricks. WATCH MARCH\u2019S RECORDING","title":"Data Cleaning"},{"location":"newsletters/2023-03/#new-template-and-custom-widget","text":"","title":"New Template and Custom Widget"},{"location":"newsletters/2023-03/#flashcards","text":"Ready to ace your next test? You\u2019ll score \ud83d\udcaf when you study with these baddies. Flashcards are one of the most effective forms of study \u2014 and with Grist they\u2019re all in one place. GO TO TEMPLATE","title":"Flashcards"},{"location":"newsletters/2023-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-03/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. More Languages to Choose From # Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week. Dev Talk # This month we\u2019re highlighting cool side projects that Grist engineers are passionate about. Grist Electron App # Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f Why Sorting Is Harder Than It Seems # Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting . Large Docs Bogging You Down? # Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up. Learning Grist # Webinar: Data Cleaning # Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR Working with Dates # In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING Templates # Task Management # Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE Payroll # Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/02"},{"location":"newsletters/2023-02/#february-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill February 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2023 Newsletter"},{"location":"newsletters/2023-02/#more-languages-to-choose-from","text":"Last month we announced that Grist is now available in multiple languages thanks to the efforts of volunteer translators. Since then, more volunteers have come forward, far exceeding our expectations. Big thank you to everyone who has translated Grist! \ud83d\ude4f It is now translated into the following languages: Chinese French German Italian Portuguese Russian Spanish Ukrainian There are also partial translations of Norwegian Bokm\u00e5l and Polish. If you\u2019re interested in completing those translations, visit our Weblate project to contribute to those efforts. If you would like us to add a new language to Weblate, let us know which language in this community thread . Note that the Chinese translation is brand new and will be available in the product next week.","title":"More Languages to Choose From"},{"location":"newsletters/2023-02/#dev-talk","text":"This month we\u2019re highlighting cool side projects that Grist engineers are passionate about.","title":"Dev Talk"},{"location":"newsletters/2023-02/#grist-electron-app","text":"Paul has been working on an Electron build of Grist! It is a longstanding passion project of his to make Grist a standalone app on Windows, Mac and Linux. Last year, user @stan-donarise worked on a Grist Electron app for Windows 7 or later . Paul took ideas from that effort, from this forum thread , and from an early standalone version of Grist developed at Grist Labs. Check out the Grist Electron app repo on Github . Use with your own Grist documents or documents you trust since there is no sandboxing \u2014 yet. \ud83d\ude0f","title":"Grist Electron App"},{"location":"newsletters/2023-02/#why-sorting-is-harder-than-it-seems","text":"Every once in a while a user will report a sorting bug. Some developers may think sorting is easy. Here\u2019s why it\u2019s not, and a delightful deep dive into the unexpected corners of sorting .","title":"Why Sorting Is Harder Than It Seems"},{"location":"newsletters/2023-02/#large-docs-bogging-you-down","text":"Our engineers also worked on optimizations to improve formula performance. The Python profiling tool py-spy was particularly helpful to know where CPU time was being spent. Tests on a large document showed a 26% improvement in performance speed. Your mileage may vary, and we\u2019d be curious to hear if anyone noticed the speed up.","title":"Large Docs Bogging You Down?"},{"location":"newsletters/2023-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-02/#webinar-data-cleaning","text":"Typically after importing data into Grist, the next step is cleaning that data. We\u2019ll share tips and tricks to sanitize data efficiently, including some clever Grist-only hacks. Thursday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR","title":"Webinar: Data Cleaning"},{"location":"newsletters/2023-02/#working-with-dates","text":"In February, we learned how to work with dates in filters, summary tables, and formulas. WATCH FEBRUARY\u2019S RECORDING","title":"Working with Dates"},{"location":"newsletters/2023-02/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-02/#task-management","text":"Organize your team\u2019s tasks and guide a weekly check-in. GO TO TEMPLATE","title":"Task Management"},{"location":"newsletters/2023-02/#payroll","text":"Our simple payroll tracker is one of our most popular templates. Track employee wage information, payroll payments, and pay period payroll spending in one place. GO TO TEMPLATE","title":"Payroll"},{"location":"newsletters/2023-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-02/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2023-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2023 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch! # Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in. Expanding Widgets # Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner. View As Another User # Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel. Seed Rules for Granular Table Permission # When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed. One-click Toggle to Deny Editor Schema Permission # By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox. Document Settings Have Moved # You can now find document settings in the \u201cTools\u201d section of the left-side panel. Community Highlights # @jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f Learning Grist # Webinar: Working with Dates # Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR Access Rules for Teams # In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING Templates # Habit Tracker # Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE Credit Card Expenses # Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE Recruiting # Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2023/01"},{"location":"newsletters/2023-01/#january-2023-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill January 2023 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2023 Newsletter"},{"location":"newsletters/2023-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2023-01/#grist-en-francais-espanol-portugues-und-deutsch","text":"Grist is now available in multiple languages. To set a language, click on your user icon > profile settings > select a language from the drop-down menu. We are so grateful to the volunteers who translated Grist, namely the team at ANCT and Paul Janzen. \ud83d\ude4f The ANCT developers also did the hard work of making a good chunk of the application translatable. Merci bien! Want to help translate Grist? We use Weblate to manage translations and welcome volunteer translators. Thanks to everyone who is pitching in.","title":"Grist en Fran\u00e7ais, Espa\u00f1ol, Portugu\u00eas und Deutsch!"},{"location":"newsletters/2023-01/#expanding-widgets","text":"Working on small screens just got easier! It is now possible to expand widgets by clicking on the expand icon in the top-right corner.","title":"Expanding Widgets"},{"location":"newsletters/2023-01/#view-as-another-user","text":"Viewing a document as different team members is useful when testing access rules. It is now easier to cycle through members in a \u201cView As\u201d banner without leaving the page. You can also view as a team member from the three-dot menu in the Access Rules button in the left-side panel.","title":"View As Another User"},{"location":"newsletters/2023-01/#seed-rules-for-granular-table-permission","text":"When writing access rules, it\u2019s fairly common to repeat the same rule across many tables \u2014 for example, always grant owners full read and write permissions. There is now a checkbox that will automatically grant owners full access whenever table rules are added. Rules that are automatically added to new table rules are called seed rules. You can modify seed rules and even add memos as needed.","title":"Seed Rules for Granular Table Permission"},{"location":"newsletters/2023-01/#one-click-toggle-to-deny-editor-schema-permission","text":"By default, editors of a document have schema permissions, which allow them to modify a document\u2019s structure, views, and formulas. Formulas are powerful because a determined user can retrieve information they don\u2019t have access to by using formulas. To protect against that, editors can be denied schema permissions with an additional access rule. We\u2019ve made that easier with a one-click checkbox.","title":"One-click Toggle to Deny Editor Schema Permission"},{"location":"newsletters/2023-01/#document-settings-have-moved","text":"You can now find document settings in the \u201cTools\u201d section of the left-side panel.","title":"Document Settings Have Moved"},{"location":"newsletters/2023-01/#community-highlights","text":"@jperon created and shared a QR code custom widget. @enthus1ast created an app that periodically backs up Grist documents in SQLite, Xlsx, and CSV file formats. Thank you to both! \ud83d\ude4f","title":"Community Highlights"},{"location":"newsletters/2023-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2023-01/#webinar-working-with-dates","text":"Dates are a common part of data. However, working with them in formulas and filters can be daunting. In this month\u2019s webinar, we\u2019ll share tips for working with dates. Thursday February 16th at 2:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR","title":"Webinar: Working with Dates"},{"location":"newsletters/2023-01/#access-rules-for-teams","text":"In January, we learned how to write access rules to manage team members\u2019 permissions on a document. WATCH JANUARY\u2019S RECORDING","title":"Access Rules for Teams"},{"location":"newsletters/2023-01/#templates","text":"","title":"Templates"},{"location":"newsletters/2023-01/#habit-tracker","text":"Set yourself up for success by building better habits! Track your progress with this simple weekly habit tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2023-01/#credit-card-expenses","text":"Track employee credit card expenses and analyze spending in one place. GO TO TEMPLATE","title":"Credit Card Expenses"},{"location":"newsletters/2023-01/#recruiting","text":"Use this template to track candidates applying for roles on your team. Collaboratively track job applicants, and attach resumes and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2023-01/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2023-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2023-01/#we-are-here-to-support-you","text":"Sprouts Program. Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. Learn more. Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # New Date Filter with Calendar # Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day. Snapshots in Grist Core # Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots . Quick Delete for Invalid Table/Column Access Rules # If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted. Improved UI for Memo Writing # Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule. Tips # To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox. Open Source Contributions # Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget . Learning Grist # Webinar: Access Rules for Teams # Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Modifying Templates # In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Church Management # Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE Book Club # A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/12"},{"location":"newsletters/2022-12/#december-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill December 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2022 Newsletter"},{"location":"newsletters/2022-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-12/#new-date-filter-with-calendar","text":"Filtering date and datetime columns just got easier. You can now select a date range using a calendar picker. The bounds of the range may also be relative \u2014 for example, from 1st day of this month to Today . The filter will update with each passing day.","title":"New Date Filter with Calendar"},{"location":"newsletters/2022-12/#snapshots-in-grist-core","text":"Previously, snapshots were available on hosted Grist and the self-managed enterprise plan, and relied on commercial cloud storage services. Now, snapshot support is also available on Grist Core \u2014 the open source version of Grist \u2014 and can work with open source self-hosted storage options such as MinIO. \ud83c\udf89 Learn how to set up snapshots .","title":"Snapshots in Grist Core"},{"location":"newsletters/2022-12/#quick-delete-for-invalid-tablecolumn-access-rules","text":"If there are access rules for a table or column that has been deleted, there is now a convenient button to delete the rules that apply to the deleted column or table. You remain in control of which rules remain and which are deleted, so you don\u2019t have to worry about rules disappearing if you are reorganizing your document. In this example, the Breeder_Retirement column has been deleted, so the column rule for that column should also be deleted.","title":"Quick Delete for Invalid Table/Column Access Rules"},{"location":"newsletters/2022-12/#improved-ui-for-memo-writing","text":"Access rules now have a memo button to add memos to rules, making writing and editing memos more convenient. Memos can really help colleagues understand why they can\u2019t do something, since memos are shown in a popup when an action is blocked by a rule.","title":"Improved UI for Memo Writing"},{"location":"newsletters/2022-12/#tips","text":"To help you and your teammates get the most out of Grist, there are now tips that appear as you explore Grist\u2019s features. To dismiss all tips, check the \u201cDon\u2019t show tips\u201d checkbox.","title":"Tips"},{"location":"newsletters/2022-12/#open-source-contributions","text":"Thank you to @jperon who created and shared a pivot table custom widget. Find it in our custom widget Github repo, grist-widget .","title":"Open Source Contributions"},{"location":"newsletters/2022-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-12/#webinar-access-rules-for-teams","text":"Learn how to write access rules that manage team members\u2019 permissions on a document. Thursday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR","title":"Webinar: Access Rules for Teams"},{"location":"newsletters/2022-12/#modifying-templates","text":"In December, we learned how to modify a template from our template gallery to better fit your workflow. WATCH DECEMBER\u2019S RECORDING","title":"Modifying Templates"},{"location":"newsletters/2022-12/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-12/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-12/#church-management","text":"Grist user Paul Janzen built a CRM for managing parishioner information at his church. He shared the template with us to include in our gallery. Thank you! \ud83d\ude4f GO TO TEMPLATE","title":"Church Management"},{"location":"newsletters/2022-12/#book-club","text":"A common New Year\u2019s resolution is to read more books. Kickstart reading habits with a book club! Use this template to keep track of book suggestions, and find books in stores and libraries with a click. GO TO TEMPLATE","title":"Book Club"},{"location":"newsletters/2022-12/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-12/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Grist Experiment: Writing Python Formulas with AI # We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE Want to Help Grist\u2019s Development? # We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US What\u2019s New # Sort and Filter Improvements # We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!) Learning Grist # Webinar: Modifying Templates # December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR Creator Tips for Productive Workflows # In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE Templates # Donations Tracking # It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE \ud83c\udf84 Christmas Gifts Budget # Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE Potluck Organizer # We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/11"},{"location":"newsletters/2022-11/#november-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill November 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2022 Newsletter"},{"location":"newsletters/2022-11/#grist-experiment-writing-python-formulas-with-ai","text":"We see plenty of Grist power users who use every feature except formulas, which is a shame, but understandable. Formulas can be daunting. Imagine if writing spreadsheet formulas were as simple as asking a question. We recently experimented with OpenAI to see how well AI could write Python formulas in Grist. The experiment yielded promising results which we\u2019re excited to share! While this AI-assisted formula generation feature is not yet a part of Grist, this experiment proves its future value: AI can eliminate a common struggle for today\u2019s spreadsheet user \u2014 writing formulas correctly. Read more about the experiment on our blog. There\u2019s a poll at the end where you can vote for this feature, and sign up for updates about our AI formula generation. READ MORE","title":"Grist Experiment: Writing Python Formulas with AI"},{"location":"newsletters/2022-11/#want-to-help-grists-development","text":"We\u2019re always working on big new features and would love early feedback from users before features are added to Grist. If you would like to test early features and provide feedback, email success@getgrist.com . EMAIL US","title":"Want to Help Grist\u2019s Development?"},{"location":"newsletters/2022-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-11/#sort-and-filter-improvements","text":"We\u2019ve improved the experience of sorting and filtering tables and other widgets, making it easier to use. Save and revert settings are now clearer at a glance, and the filter buttons are more flexible. Previously, to create filter buttons above a widget, you could either make all or none of your filters into buttons. Now, you can pin filter buttons individually. For example, this widget has two filters applied to the \u201cInvitation\u201d and \u201cThank You Sent?\u201d columns. Only one of them, \u201cThank you Sent?\u201d, is pinned as a button for convenience. \u201cInvitation\u201d is not pinned, because it is ensuring that this view only shows guests who RSVP\u2019d yes to the wedding. (If you need it, here\u2019s the full wedding planning template \u2014 and congrats!)","title":"Sort and Filter Improvements"},{"location":"newsletters/2022-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-11/#webinar-modifying-templates","text":"December\u2019s webinar will focus on how to look under the hood of a template from our template gallery , and modify it to meet your needs. Thursday December 15th at 3:00pm US Eastern Time. SIGN UP FOR DECEMBER\u2019S WEBINAR","title":"Webinar: Modifying Templates"},{"location":"newsletters/2022-11/#creator-tips-for-productive-workflows","text":"In November, Natalie shared tips for creators to get the most out of Grist while building productive workflows for teams, while making use of features that make Grist easy to use by anyone. WATCH NOVEMBER\u2019S RECORDING","title":"Creator Tips for Productive Workflows"},{"location":"newsletters/2022-11/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-11/#templates","text":"","title":"Templates"},{"location":"newsletters/2022-11/#donations-tracking","text":"It\u2019s always the season for giving! Keep track of your charitable donations all in one place. For more guidance on how to use this template, read our blog. GO TO TEMPLATE","title":"Donations Tracking"},{"location":"newsletters/2022-11/#christmas-gifts-budget","text":"Tis the season! Easily track and budget your holiday gifts in one place. GO TO TEMPLATE","title":"\ud83c\udf84 Christmas Gifts Budget"},{"location":"newsletters/2022-11/#potluck-organizer","text":"We\u2019re re-sharing this template because it may still be useful heading into the holiday season! This template handles the logistics of potluck dinners. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. For guidance on how to use it, read our blog . GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-11/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Quick Sum # Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09 Duplicate Table # You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data. New Table and Column API Methods # You may now add, modify and list tables and columns in a document. See our REST API reference to learn more Multi-column Formatting # You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time. New Add + Remove Rows Shortcut # Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) New PHONE_FORMAT() Function # Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT(). Learning Grist # Webinar: Building Team Workflows # In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR Team Basics # In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING Sprouts Program # Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE New Templates # Novel Planning # Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE Potluck Organizer # We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE Wedding Planner # Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/10"},{"location":"newsletters/2022-10/#october-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill October 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2022 Newsletter"},{"location":"newsletters/2022-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-10/#quick-sum","text":"Need to quickly sum or count values? Select a range, and a handy total and count will appear in the footer of the widget. Finally! \ud83d\ude09","title":"Quick Sum"},{"location":"newsletters/2022-10/#duplicate-table","text":"You can now quickly duplicate a table! Go to the raw data page and open the table\u2019s menu. You may duplicate a table\u2019s structure with or without the table\u2019s data.","title":"Duplicate Table"},{"location":"newsletters/2022-10/#new-table-and-column-api-methods","text":"You may now add, modify and list tables and columns in a document. See our REST API reference to learn more","title":"New Table and Column API Methods"},{"location":"newsletters/2022-10/#multi-column-formatting","text":"You may now select several adjacent columns and edit their column type and formatting simultaneously, saving you time.","title":"Multi-column Formatting"},{"location":"newsletters/2022-10/#new-add-remove-rows-shortcut","text":"Shortcuts to remove or insert a record have changed, to avoid interfering with page zoom. Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s)","title":"New Add + Remove Rows Shortcut"},{"location":"newsletters/2022-10/#new-phone_format-function","text":"Formatting phone numbers just got easier with a new helpful function. Learn more about PHONE_FORMAT().","title":"New PHONE_FORMAT() Function"},{"location":"newsletters/2022-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-10/#webinar-building-team-workflows","text":"In November we\u2019re continuing our Grist for Teams series. November\u2019s webinar will focus on how to build productive workflows for teams, while making use of features that make Grist easy to use by anyone on your team. Tuesday November 22nd at 3:00pm US Eastern Time. SIGN UP FOR NOVEMBER\u2019S WEBINAR","title":"Webinar: Building Team Workflows"},{"location":"newsletters/2022-10/#team-basics","text":"In October, Natalie walked us through team basics: team sites, workspaces, sharing, and more. WATCH OCTOBER\u2019S RECORDING","title":"Team Basics"},{"location":"newsletters/2022-10/#sprouts-program","text":"Grist often surprises people with its capabilities. Schedule a free Sprouts call with an expert to see if Grist can address your needs. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-10/#novel-planning","text":"Every writer has their own unique process \u2013 but for most, that process involves some level of planning. Our Novel Planning Template is just in time for this year\u2019s NaNoWriMo . Keep your novel structure, outline, research and notes all in one place! Want more guidance on how to use this template? Read our blog. GO TO TEMPLATE","title":"Novel Planning"},{"location":"newsletters/2022-10/#potluck-organizer","text":"We made this template with Thanksgiving in mind, but it works for all potluck dinners! Potluck logistics can be tricky to handle, but this template makes it easy to juggle who brings what. Everyone can collaborate by writing down what they\u2019re bringing and making sure all the essentials are covered. GO TO TEMPLATE","title":"Potluck Organizer"},{"location":"newsletters/2022-10/#wedding-planner","text":"Recently engaged? \ud83d\udc8d Congratulations! Planning a wedding can be chaotic but not for you because you have Grist \ud83d\ude0a Use this template to organize vendor notes, guest lists, contracts and more! GO TO TEMPLATE","title":"Wedding Planner"},{"location":"newsletters/2022-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-10/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Dark Mode \ud83d\udd76 # Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting. Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc! Improved User Management with Autocomplete # When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd. Export Table as XLSX # It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents. Learning Grist # Webinar: Team Sites # Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR Link Keys # On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Template # Event Volunteering # Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/09"},{"location":"newsletters/2022-09/#september-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill September 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2022 Newsletter"},{"location":"newsletters/2022-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-09/#dark-mode","text":"Grist now comes in dark mode. \ud83d\ude0e To work in dark mode, click on your user icon > profile settings > select \u201cDark\u201d under the appearance setting.","title":"Dark Mode \ud83d\udd76"},{"location":"newsletters/2022-09/#open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights from a team that\u2019s not only using Grist, but is making it better for everyone. Thank you, @LouisDelbosc!","title":"Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-09/#improved-user-management-with-autocomplete","text":"When adding users to a document, the list will now autocomplete with emails of team members. This is currently available on grist-core and will land in hosted Grist by Monday, October 3rd.","title":"Improved User Management with Autocomplete"},{"location":"newsletters/2022-09/#export-table-as-xlsx","text":"It is now possible to export a table as an XLSX from the widget menu. Learn more about exporting tables v. documents.","title":"Export Table as XLSX"},{"location":"newsletters/2022-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-09/#webinar-team-sites","text":"Next month we\u2019re kicking off a series focused on Grist for Teams . This series of four videos will cover everything you need to know about team sites, workspaces, collaboration, and access rules. In October, it\u2019s back to team basics: team sites, workspaces, sharing, and more. Thursday October 20th at 3:00pm US Eastern Time. SIGN UP FOR OCTOBER\u2019S WEBINAR","title":"Webinar: Team Sites"},{"location":"newsletters/2022-09/#link-keys","text":"On September\u2019s webinar, Natalie demonstrated how to use Grist\u2019s link keys to share partial data via unique links. WATCH SEPTEMBER\u2019S RECORDING","title":"Link Keys"},{"location":"newsletters/2022-09/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-09/#new-template","text":"","title":"New Template"},{"location":"newsletters/2022-09/#event-volunteering","text":"Use this template to keep track of which volunteers are working which events. Link keys with access rules grant specific access for each volunteer, allowing them to view their volunteer schedule and event details without logging into Grist. Want to learn how we built this? Watch Natalie build it on September\u2019s webinar. GO TO TEMPLATE","title":"Event Volunteering"},{"location":"newsletters/2022-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-09/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties. Free Team Sites # Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE What\u2019s New # Conditional Row Styles # You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab. More Helpful Formula Errors + Autocomplete # Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet . Open Raw Data from Widget # You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets. Left Pane Now Auto Expands # Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89 Hide Multiple Columns # You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click. Community & Open Source Contributions \ud83d\ude4f # Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights. Quickly Rename Pages # To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc! Custom Widgets: Add Column Description in Creator Panel # For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface! Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb # grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist. Learning Grist # Webinar: Sharing Partial Data with Link Keys # In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR Relational Data + Reference Columns # In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Team Meetings Organizer # Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE Personal Notebook # Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/08"},{"location":"newsletters/2022-08/#august-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill August 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Free Team Sites Unlimited team members + documents, for documents under 5,000 rows. Conditional Row Styles! Apply conditional styles to an entire row. Better Formula Help More helpful autocomplete & error messages. \ud83d\udcaa Open Source Community Shout Out! Highlights of cool contributions by OSS community. \ud83d\ude4f 2 new templates Meetings organizer & personal notebook, built by awesome users! \ud83c\udf89 Webinar: Link Keys Sharing partial data with third parties.","title":"August 2022 Newsletter"},{"location":"newsletters/2022-08/#free-team-sites","text":"Grist is most powerful when used collaboratively. That\u2019s why we\u2019re now offering free team sites for your whole team. Unlimited team members + unlimited documents, for documents under 5,000 rows. Free team sites are fully featured, too. CREATE FREE TEAM SITE","title":"Free Team Sites"},{"location":"newsletters/2022-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-08/#conditional-row-styles","text":"You can now apply conditional styles to an entire row. In the creator panel, it\u2019s under the Table > Widget tab.","title":"Conditional Row Styles"},{"location":"newsletters/2022-08/#more-helpful-formula-errors-autocomplete","text":"Writing and troubleshooting formulas just got a little bit easier with more helpful autocomplete suggestions and error messages. For example, in the screenshot below, the autocomplete helps write the common but hard-to-remember formula for reverse lookups , and the error includes a human-friendly explanation, and helpful suggestions to fix it. Want to learn more about formulas? There is a lot about formulas in our Help Center, including this recently-added formula cheat sheet .","title":"More Helpful Formula Errors + Autocomplete"},{"location":"newsletters/2022-08/#open-raw-data-from-widget","text":"You can open a widget\u2019s source table without leaving the page. In the widget menu, click on \u201cShow raw data\u201d. This is particularly handy for charts and custom widgets.","title":"Open Raw Data from Widget"},{"location":"newsletters/2022-08/#left-pane-now-auto-expands","text":"Did you know you can collapse the left-side page menu to give yourself more space? Now, the collapsed menu auto-expands on hover, giving you more room on small screens! \ud83c\udf89","title":"Left Pane Now Auto Expands"},{"location":"newsletters/2022-08/#hide-multiple-columns","text":"You may now select multiple columns, right click to open the column menu, and hide all selected columns with a click.","title":"Hide Multiple Columns"},{"location":"newsletters/2022-08/#community-open-source-contributions","text":"Grist is open source . We\u2019re grateful to the open source community for their contributions. Here are two recent highlights.","title":"Community & Open Source Contributions \ud83d\ude4f"},{"location":"newsletters/2022-08/#quickly-rename-pages","text":"To rename a page name, all you need to do now is click. Thank you, @LouisDelbosc!","title":"Quickly Rename Pages"},{"location":"newsletters/2022-08/#custom-widgets-add-column-description-in-creator-panel","text":"For custom widget devs: when specifying what columns your widget needs access to, you can now include a description to help guide your widget users, as shown here . Thank you, @yohanboniface!","title":"Custom Widgets: Add Column Description in Creator Panel"},{"location":"newsletters/2022-08/#open-source-cool-dev-highlights","text":"grist-core omnibus. Paul published a grist-core omnibus to make self-hosting easier. Grist as an electron app. User stan-donarise packaged Grist as an electron app that works on Windows 7 or later. How cool is that! \ud83d\ude09 Generate docx from template. User stan-donarise didn\u2019t stop there. He integrated docxtemplater into a custom widget to generate reports and documents in Grist.","title":"Open Source + Cool Dev Highlights \ud83d\udc69\u200d\ud83d\udcbb"},{"location":"newsletters/2022-08/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-08/#webinar-sharing-partial-data-with-link-keys","text":"In September we\u2019ll explain how to use one of Grist\u2019s coolest and least explored features: link keys. Learn how to use Grist\u2019s link keys to share partial data, such as a single row, with third parties. Thursday September 22nd at 3:00pm US Eastern Time. SIGN UP FOR SEPTEMBER\u2019S WEBINAR","title":"Webinar: Sharing Partial Data with Link Keys"},{"location":"newsletters/2022-08/#relational-data-reference-columns","text":"In August, Natalie dove deep into reference columns and how to use them in dashboards and formulas. WATCH AUGUST\u2019S RECORDING","title":"Relational Data + Reference Columns"},{"location":"newsletters/2022-08/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-08/#team-meetings-organizer","text":"Use this template to store team meeting minutes, action items, project documentation and more. Thank you to Grist user Eduardo who shared this template with us. It\u2019s been lightly modified to meet more general purpose needs. Want to learn how this was built? Watch Natalie build it on August\u2019s webinar. GO TO TEMPLATE","title":"Team Meetings Organizer"},{"location":"newsletters/2022-08/#personal-notebook","text":"Organize your projects, contact info, and tasks in this personal notebook. This template was built by Grist user Julien. It has been translated and lightly modified to meet more general purpose needs. Check out his showcase in the community forum! If you have a cool template you\u2019d like to share, we\u2019d love to hear about it in the community showcase! GO TO TEMPLATE","title":"Personal Notebook"},{"location":"newsletters/2022-08/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-08/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Formula Cheat Sheet # New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE Summary Tables in Raw Data # Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about. Learning Grist # Webinar: Relational Data and Reference Columns # August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR How to structure your data # In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING Community Highlights # Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate. Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE New Templates # Sales Commission Dashboard # Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE User Feedback Responses # Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE Net Promoter Score Results # Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/07"},{"location":"newsletters/2022-07/#july-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill July 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2022 Newsletter"},{"location":"newsletters/2022-07/#formula-cheat-sheet","text":"New to Grist formulas and Python? Here\u2019s a formula cheat sheet with the most commonly used functions and plenty of examples. GO TO CHEAT SHEET If you want extra formula help, we also have an in depth article focused on references and lookups with more examples. GO TO ARTICLE","title":"Formula Cheat Sheet"},{"location":"newsletters/2022-07/#summary-tables-in-raw-data","text":"Summary tables are now listed in raw data, making it easier to see at a glance what summary tables exist anywhere in the document, and examine any of them. You can easily identify summary tables by the sigma icon. Note also the friendlier names for summary tables, like Sales_summary . Anyone who has ever noticed GristSummary_5_Sales will know what we are talking about.","title":"Summary Tables in Raw Data"},{"location":"newsletters/2022-07/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-07/#webinar-relational-data-and-reference-columns","text":"August\u2019s webinar will focus on how to relate data in Grist using reference columns, how to use related data in dynamic dashboards, and how to perform lookups. Thursday August 18th at 3:00pm US Eastern Time. SIGN UP FOR AUGUST\u2019S WEBINAR","title":"Webinar: Relational Data and Reference Columns"},{"location":"newsletters/2022-07/#how-to-structure-your-data","text":"In July, Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JULY\u2019S RECORDING","title":"How to structure your data"},{"location":"newsletters/2022-07/#community-highlights","text":"Summarizing data with a pie chart. Learn how to create pie charts in Grist. Find and highlight duplicates with conditional formatting. Learn how to set conditional styles based on a formula that returns whether or not a value is a duplicate.","title":"Community Highlights"},{"location":"newsletters/2022-07/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-07/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-07/#sales-commission-dashboard","text":"Sales and accounting teams can use this template to track sales and commission. Sales reps only see their individual sales, and the overall leaderboard. Management can see everything. GO TO TEMPLATE","title":"Sales Commission Dashboard"},{"location":"newsletters/2022-07/#user-feedback-responses","text":"Improve your product based on customer feedback! Analyze and evaluate your user survey responses. GO TO TEMPLATE","title":"User Feedback Responses"},{"location":"newsletters/2022-07/#net-promoter-score-results","text":"Keep your finger on the pulse of your customers\u2019 satisfaction. Analyze and drill into your NPS survey responses. GO TO TEMPLATE","title":"Net Promoter Score Results"},{"location":"newsletters/2022-07/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-07/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas. Happy Pride! # Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET What\u2019s New # Range Filtering # It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future. PEEK() # PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error. Learning Grist # Webinar: Structuring Data in Grist # Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Quick Tips # All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url. Community Highlights # Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values. New Templates # Expense Tracking for Teams # Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE Grocery List + Meal Planner # Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/06"},{"location":"newsletters/2022-06/#june-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill June 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Range Filtering Happy Pride! Check out our rainbow spreadsheet! \ud83c\udf08 Webinar: Structuring Relational Data Best tips on how to structure data in Grist. 2 New Templates Expense tracking and meal planning. PEEK(a-boo) Special new function to resolve circular references in trigger formulas.","title":"June 2022 Newsletter"},{"location":"newsletters/2022-06/#happy-pride","text":"Hope you had a fun and colorful Pride month! We did. Rainbow spreadsheet! \ud83c\udff3\u200d\ud83c\udf08 GO TO RAINBOW SPREADSHEET","title":"Happy Pride!"},{"location":"newsletters/2022-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-06/#range-filtering","text":"It is now possible to filter Numeric and Date columns by a range! Keep an eye out for more improvements to filtering in the near future.","title":"Range Filtering"},{"location":"newsletters/2022-06/#peek","text":"PEEK () evaluates an expression without creating dependencies or requiring that referenced values are up to date, and uses whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. Learn more. Here\u2019s an example of PEEK() in action. Note that in the trigger formula column, the two formulas can reference each other without creating a circular error.","title":"PEEK()"},{"location":"newsletters/2022-06/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-06/#webinar-structuring-data-in-grist","text":"Before you can build productive layouts, you have to think through how to structure your data. In July, we\u2019ll share some best practices and highlight the differences in how data is structured in spreadsheets v. Grist. Thursday July 21st at 3:00pm US Eastern Time. SIGN UP FOR JULY\u2019S WEBINAR In June Anais demonstrated how Grist can simplify data workflows, such as expense tracking, when compared to traditional spreadsheets. WATCH JUNE\u2019S RECORDING","title":"Webinar: Structuring Data in Grist"},{"location":"newsletters/2022-06/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-06/#quick-tips","text":"All formula columns in summary tables can be series in charts based on that summary table. For example, in the General Ledger template , the Profit columns in the summary tables are used in the charts. There are additional sort options in the creator panel\u2019s sort menu under the three dots icon. In case you missed it. The Grist video player is a custom widget that plays a video from a URL, similar to how the image viewer displays an image from a url.","title":"Quick Tips"},{"location":"newsletters/2022-06/#community-highlights","text":"Conditional trigger formulas. Use a trigger formula to conditionally autofill data in some cases, but not others. Setting default value for reference column. Use trigger formulas to set default reference values.","title":"Community Highlights"},{"location":"newsletters/2022-06/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-06/#expense-tracking-for-teams","text":"Manage all employee expenses in one spreadsheet! No more wrangling dozens of employees\u2019 expenses into a master list. With access rules, employees can log into Grist, view and update only their expenses, and Grist summary tables take care of the rest. GO TO TEMPLATE","title":"Expense Tracking for Teams"},{"location":"newsletters/2022-06/#grocery-list-meal-planner","text":"Create recipes, build weekly menus and make a grocery list in one place! GO TO TEMPLATE","title":"Grocery List + Meal Planner"},{"location":"newsletters/2022-06/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-06/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing. What\u2019s New # Raw Data Tables # Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view. Linking Referenced Data to Summary Tables # Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself. API Endpoint GET /attachments # New API endpoint. /attachments will return list of all attachment metadata. Learn more. Access Details and Leave a Document # Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document. New Keyboard Shortcut # New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. Learning Grist # Webinar: Expense Tracking in Grist v Excel # Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods. New Templates # Hurricane Preparedness # Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE Gig Staffing # Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/05"},{"location":"newsletters/2022-05/#may-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill May 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Raw Data Page! Link Related Data to Summary Tables When linking summary tables and other widgets, you can now link on reference columns too. Webinar: Expense Tracking in Grist v. Excel How to build efficient workflows in Grist. Shortcut to Duplicate Rows Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac. 2 New Templates Hurricane preparedness and gig staffing.","title":"May 2022 Newsletter"},{"location":"newsletters/2022-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-05/#raw-data-tables","text":"Introducing the raw data page, a special page that lists all data tables in your document, making it easier for you to find and browse all your data. Learn more. Raw Data has been a long time in coming! In Grist, there has always been a conceptual separation between views and underlying data. As a Grist creator, you have probably found yourself in the shoes of an application developer: at one time thinking about relationships in the data, and separately designing how to present this data in convenient productive views. Raw data makes the separation explicit. It\u2019s a place where you can always find the underlying data, and its structure. When creating pages, your page list can now be limited to only the most useful views, without the clutter of helper tables. Those are still available any time in the raw data view.","title":"Raw Data Tables"},{"location":"newsletters/2022-05/#linking-referenced-data-to-summary-tables","text":"Tables that reference a summary table\u2019s underlying data may now be linked to the summary table itself.","title":"Linking Referenced Data to Summary Tables"},{"location":"newsletters/2022-05/#api-endpoint-get-attachments","text":"New API endpoint. /attachments will return list of all attachment metadata. Learn more.","title":"API Endpoint GET /attachments"},{"location":"newsletters/2022-05/#access-details-and-leave-a-document","text":"Non-owners of documents may now view their access details and leave a document if they wish. Click on the share icon in the top-right, then \u201cAccess Details.\u201d From the access details pop up, you may click the trash icon to leave a document.","title":"Access Details and Leave a Document"},{"location":"newsletters/2022-05/#new-keyboard-shortcut","text":"New keyboard shortcut for duplicating selected record(s)! Ctrl + Shift + D on Windows, or \u2318 \u21e7 D on Mac.","title":"New Keyboard Shortcut"},{"location":"newsletters/2022-05/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-05/#webinar-expense-tracking-in-grist-v-excel","text":"Tracking expenses in a spreadsheet can be convenient, but traditional spreadsheets require a lot of maintenance \u2014 especially with formulas. Grist can make expense tracking easier. Join us to learn how. Thursday June 16th at 3:00pm US Eastern Time. SIGN UP FOR JUNE\u2019S WEBINAR In May we went back to basics. Natalie took a data workflow in Google Sheets and transformed it into a productive dashboard in Grist. WATCH APRIL\u2019S RECORDING","title":"Webinar: Expense Tracking in Grist v Excel"},{"location":"newsletters/2022-05/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-05/#community-highlights","text":"Sequential numbers using formulas. Check out this thread with a few examples on how to get sequential numbers in Grist. Concatenating data in columns of different types. Concatenating text is simple, but what if you want to concatenate different types of data, such as text, reference columns, and dates? Learn how. Numbers that begin with zero. Natalie shares two methods.","title":"Community Highlights"},{"location":"newsletters/2022-05/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-05/#hurricane-preparedness","text":"Do you live on the coast? Don\u2019t stress! Use this template to ensure you have everything you need in the event of a hurricane. GO TO TEMPLATE","title":"Hurricane Preparedness"},{"location":"newsletters/2022-05/#gig-staffing","text":"Easily manage staffing for gigs and track employee timesheets. This sample data is focused on staffing for shows at a theater, but the template can be modified for catering, event staffing, and more. GO TO TEMPLATE","title":"Gig Staffing"},{"location":"newsletters/2022-05/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-05/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix. What\u2019s New # Rich Text Editor # Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets. New Font and Color Selector # The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting . Copying Column Settings # If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied. New Zapier Action - Create or Update Record # There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint. Dropbox Embedder # If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets. Learning Grist # Webinar: Back to Basics # We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING Sprouts Program # Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE Community Highlights # Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how. New Templates # U.S. National Parks Database # Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE Simple Time Tracker # It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE Covey Time Management Matrix # Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/04"},{"location":"newsletters/2022-04/#april-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill April 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Rich Text Editor! National Parks Database in Grist! A community-maintained resource and application built entirely in Grist. New Font Options! Bold, italicize, underline, and strikethrough your text. Next webinar \u2013 back to basics How to build efficient workflows in Grist. 2 new templates Simple time tracker and Covey time management matrix.","title":"April 2022 Newsletter"},{"location":"newsletters/2022-04/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-04/#rich-text-editor","text":"Add a notepad widget to your document and display a column\u2019s data in a rich text editor. In the custom widget menu, select \u201cNotepad\u201d from the list of premade widgets.","title":"Rich Text Editor"},{"location":"newsletters/2022-04/#new-font-and-color-selector","text":"The color picker just got more colorful! Spice up your choices and columns with more font and color options. Bold , italic , underline , and strikethrough is here! These options are also available with conditional formatting .","title":"New Font and Color Selector"},{"location":"newsletters/2022-04/#copying-column-settings","text":"If you copy cells into an empty column, the original column\u2019s type and options will also be copied. This includes numeric and date formatting, unconditional cell colors, and choice configurations. Note that conditional rules will not be copied.","title":"Copying Column Settings"},{"location":"newsletters/2022-04/#new-zapier-action-create-or-update-record","text":"There is a new Grist action in Zapier. When importing external data via Zapier, you may now update existing records based on a merge key or, if there\u2019s no match, create a new record. Learn more on Zapier. This builds on the recently added add-or-update API endpoint.","title":"New Zapier Action - Create or Update Record"},{"location":"newsletters/2022-04/#dropbox-embedder","text":"If you store files in Dropbox, embed your files right in Grist. Select \u201cDropbox Embedder\u201d from the list of custom widgets.","title":"Dropbox Embedder"},{"location":"newsletters/2022-04/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-04/#webinar-back-to-basics","text":"We get asked all the time how to get started in Grist. The best way to demonstrate is with a real use case. We\u2019ll take a spreadsheet workflow and improve it in Grist. Thursday May 19th at 3:00pm US Eastern Time. SIGN UP FOR MAY\u2019S WEBINAR On April\u2019s webinar, Natalie walked through how to configure the most useful custom widgets. WATCH APRIL\u2019S RECORDING","title":"Webinar: Back to Basics"},{"location":"newsletters/2022-04/#sprouts-program","text":"Get up and running fast with expert help. If you know what you need, but need help building it, the Sprouts program may be for you. LEARN MORE","title":"Sprouts Program"},{"location":"newsletters/2022-04/#community-highlights","text":"Using toggle columns to create button-like experience. Josh uses Grist on mobile to track inventory in his flower shop. He shared how he uses toggle columns as buttons to streamline his workflow. Summarize data from multiple tables. Sharpen your Python skills by following Natalie\u2019s guide in how to pull in data from multiple tables to the same summary table. For self-hosters, a template for Grist with traefik and Docker compose. Running Grist on your own computer is pretty easy. Hosting it to share with others requires a few more steps. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-04/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-04/#us-national-parks-database","text":"Your one-stop resource for all national parks information! Look up parks by designation, state, activities, and more. Leave reviews and track your trips! This database is crowdsourced and community-maintained. There\u2019s a page in the database to submit suggestions and corrections. If you feel inspired to make your own crowdsourced database, check out also the simpler crowdsourced list example . Reach out on our Community Forum to get help or share the result! GO TO TEMPLATE","title":"U.S. National Parks Database"},{"location":"newsletters/2022-04/#simple-time-tracker","text":"It\u2019s like a stopwatch in a spreadsheet! With the added benefit of creating a log of total time spent on tasks. GO TO TEMPLATE","title":"Simple Time Tracker"},{"location":"newsletters/2022-04/#covey-time-management-matrix","text":"Organize your to-do list with the Covey time management method. Prioritize by what\u2019s urgent and important. GO TO TEMPLATE","title":"Covey Time Management Matrix"},{"location":"newsletters/2022-04/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f \ud83c\udf1f New! Stackshare Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-04/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9 Sprouts Program # We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry! What\u2019s New # Conditional Formatting # Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more. Improved Column Type Guessing # When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89 New API Method for Add or Update # We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more. Grist-help Is Now Public! # Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials. Learning Grist # Webinar: Custom Widgets # Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING Community Highlights # Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs. New Templates # Event Sponsors + Attendees # Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE Public Giveaway # Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE Project Management # Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/03"},{"location":"newsletters/2022-03/#march-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } .newsletter-summary { background-color: #e3fff5; margin: 0; padding: 10px; } .newsletter-summary-header { text-align: center; padding-bottom: 10px; border-bottom: 1px solid lightgrey; } .newsletter-summary ul { padding-left: 20px; } .newsletter-summary li { margin-bottom: 10px; } .newsletter-summary li p { margin: 0px } Grist for the Mill March 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. In this Newsletter Conditional Formatting! Announcing the Sprouts Program Let us help you build in Grist! Next webinar \u2013 custom widgets Learn to add maps, invoices, and other surprises. 3 new templates Events, giveaways, and more. Grist and Wordle??? Improve your Wordle game: \ud83d\udfe8\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9","title":"March 2022 Newsletter"},{"location":"newsletters/2022-03/#sprouts-program","text":"We\u2019re launching a new program to help teams get up and running fast on Grist. If you know what you need, but need help building it, we\u2019re here to help. Here\u2019s how it works: Eligibility. Anyone with a team site is eligible for the Sprouts program (even while on free trial). Schedule a free 30-minute consultation call. Email success@getgrist.com and describe your use case. We\u2019ll pair you with a Grist expert on our team and schedule a time to meet. On that call, we\u2019ll estimate the scope of the work. For quick projects. If we estimate your project can be completed in less than an hour, we\u2019ll do it ourselves \u2014 for free! Many use cases fall into this category. For larger projects. For projects longer than an hour, we\u2019ll recommend a contractor who has been trained and vetted by Grist, and has quick access to our team. Whether you choose to work with our contractor or with someone else, we\u2019ll schedule one more 30-minute call to make sure everyone understands the project. Based on this, the contractor will estimate the work and quote a price. Visit our website to submit an inquiry!","title":"Sprouts Program"},{"location":"newsletters/2022-03/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-03/#conditional-formatting","text":"Conditional formatting is finally here! \ud83c\udf8a In the creator panel in the column menu, under CELL STYLE you may now add conditional styles. Enter a formula and style the cell\u2019s background and text color. You may add multiple conditional rules. Learn more.","title":"Conditional Formatting"},{"location":"newsletters/2022-03/#improved-column-type-guessing","text":"When you import or copy and paste data, Grist is now much better at parsing data and guessing column types. Phew! That means less data cleaning when importing new data! \ud83c\udf89","title":"Improved Column Type Guessing"},{"location":"newsletters/2022-03/#new-api-method-for-add-or-update","text":"We added a new method to update an existing record, or, if it does not exist, add a new one. Learn more.","title":"New API Method for Add or Update"},{"location":"newsletters/2022-03/#grist-help-is-now-public","text":"Our Help Center is maintained in GitHub. The repo is now public , so the community can help improve our documentation and tutorials.","title":"Grist-help Is Now Public!"},{"location":"newsletters/2022-03/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-03/#webinar-custom-widgets","text":"Custom widgets have gotten easier. Learn how to configure custom widgets from the menu of premade widgets in the creator panel. Thursday April 14th at 12:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On March\u2019s webinar, Anais walked through how to set up access rules on private and public documents. WATCH MARCH\u2019S RECORDING","title":"Webinar: Custom Widgets"},{"location":"newsletters/2022-03/#community-highlights","text":"Improve your Wordle stats with Grist! David Smedberg put Grist to the test by building a document that could track his Wordle stats across multiple devices and analyze his best guess words. He shared his document on our forum . His detailed blog post about his experience is worth a read. \ud83e\udd29 Fantastic! Apply Formula to Filtered/Visible Rows Only. Grist applies formulas to the whole column. What if you only want to transform filtered rows? Here\u2019s a helpful workaround. Toggle on/off if attachment present. Suppose you have an attachment column and would like to filter for rows that have attachments. Here\u2019s how to toggle a switch to \u201con\u201d if there is an attachment. Showing images from URLs. Here\u2019s how to configure a custom widget to preview images from URLs.","title":"Community Highlights"},{"location":"newsletters/2022-03/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-03/#event-sponsors-attendees","text":"Track event registrations and ticket revenue, and reconcile sponsors and attendees. GO TO TEMPLATE","title":"Event Sponsors + Attendees"},{"location":"newsletters/2022-03/#public-giveaway","text":"Organize public giveaways on a first-come, first-serve basis. This template uses access rules to limit who can claim which gifts. GO TO TEMPLATE","title":"Public Giveaway"},{"location":"newsletters/2022-03/#project-management","text":"Keep track of tasks by department and project, and quickly flag tasks at risk GO TO TEMPLATE","title":"Project Management"},{"location":"newsletters/2022-03/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-03/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Custom Widgets Menu # Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more! Access Rules for Anonymous Users # Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL Two Factor Authentication # Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app. Cell Context Menu # Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient. Learning Grist # Webinar: Granular Access Rules # Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING Community Highlights # Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice. New Templates # Crowdsourced Lists # Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE Simple Poll # With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE Digital Sales CRM # Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE Health Insurance Comparison # Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/02"},{"location":"newsletters/2022-02/#february-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill February 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2022 Newsletter"},{"location":"newsletters/2022-02/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-02/#custom-widgets-menu","text":"Adding custom widgets just got easier! Select from a dropdown of premade widgets that are easily configurable in the creator panel. Custom widgets include Markdown editor Action button View image from URL Interactive map Invoices Print labels API inspector \u2026and more!","title":"Custom Widgets Menu"},{"location":"newsletters/2022-02/#access-rules-for-anonymous-users","text":"Granular access rules may now restrict publicly-shared, editable documents. A new user attribute, user.SessionID, makes it possible to limit which data visitors to your document may see or edit. This opens up new use cases, such as simple polling and community-moderated crowdsourced lists . TAKE OUR POLL","title":"Access Rules for Anonymous Users"},{"location":"newsletters/2022-02/#two-factor-authentication","text":"Grist now supports two factor authentication for email + password logins. Navigate to your profile settings to configure two-factor authentication with either text messages, or an authenticator app.","title":"Two Factor Authentication"},{"location":"newsletters/2022-02/#cell-context-menu","text":"Right-clicking on any cell now opens a menu that makes editing data in Grist more convenient.","title":"Cell Context Menu"},{"location":"newsletters/2022-02/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-02/#webinar-granular-access-rules","text":"Learn how to create access rules that determine who can see which columns, tables, or rows of your data. Wednesday March 16th at 3:00pm US Eastern Time. SIGN UP FOR MARCH\u2019S WEBINAR On February\u2019s webinar, Anais walked through column types and formatting, automatic backups, and how to work on forks of your document. WATCH FEBRUARY\u2019S RECORDING","title":"Webinar: Granular Access Rules"},{"location":"newsletters/2022-02/#community-highlights","text":"Document tours. Our templates often have document tours. This is not yet a polished feature for users, but it\u2019s easy to do if you would like it. Here\u2019s how. Compare a value to a corresponding value n days ago. Say you need to compare a value such as hospitalizations to hospitalizations 5 days ago. Natalie walks through a formula to do just that. Sum total from referencing records. Suppose you bill for multiple services on the same invoice, and in Grist have several Services records pointing to the same Invoice records. Learn how to sum the total hours per invoice.","title":"Community Highlights"},{"location":"newsletters/2022-02/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-02/#crowdsourced-lists","text":"Access rules make it easier to moderate crowdsourced lists. This template has access rules that allow contributors to add new records, and edit or update their contributions \u2014 without signing in. Contributors may view all records, but cannot edit or delete someone else\u2019s contributions. Moderators can edit or delete any data, and they may also add new moderators to the list. This template also makes use of the map widget. GO TO TEMPLATE","title":"Crowdsourced Lists"},{"location":"newsletters/2022-02/#simple-poll","text":"With the right set of access rules, you can create polls in Grist! This template reveals how. GO TO TEMPLATE","title":"Simple Poll"},{"location":"newsletters/2022-02/#digital-sales-crm","text":"Track the sale of digital products such as courses, e-books, membership programs, and more in a simple CRM. GO TO TEMPLATE","title":"Digital Sales CRM"},{"location":"newsletters/2022-02/#health-insurance-comparison","text":"Create an easy-to-use calculator for your employees to compare health insurance plans offered by your organization. GO TO TEMPLATE","title":"Health Insurance Comparison"},{"location":"newsletters/2022-02/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-02/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2022-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2022 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Launch and Delete Document Tours # Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d Learning Grist # Webinar: Column Types and Version Control # Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING Community Highlights # Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how. New Templates # Inventory Manager # Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE Influencer Outreach # Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE Exercise Planner # Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE Software Deals Tracker # If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius We are here to support you # Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2022/01"},{"location":"newsletters/2022-01/#january-2022-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill January 2022 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2022 Newsletter"},{"location":"newsletters/2022-01/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2022-01/#launch-and-delete-document-tours","text":"Several templates have document tours that explain the template\u2019s workflow. To relaunch the tour, click on \u201cTour of this Document\u201d near the bottom of the left panel. To delete the document tour from your copy of a template, click the little trash icon next to \u201cTour of this Document.\u201d","title":"Launch and Delete Document Tours"},{"location":"newsletters/2022-01/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2022-01/#webinar-column-types-and-version-control","text":"Work like a Grist expert! Deepen your understanding of column types, backups, and working copies of documents. Wednesday February 16th at 3:00pm US Eastern Time. SIGN UP FOR FEBRUARY\u2019S WEBINAR On January\u2019s webinar, Natalie walked through how to build productive layouts in Grist. She covered same-record and reference-record linking, card views, sorting and filtering, summary tables, and simple charts. WATCH JANUARY\u2019S RECORDING","title":"Webinar: Column Types and Version Control"},{"location":"newsletters/2022-01/#community-highlights","text":"Pull up Gmail history for a particular contact. If you store contacts in Grist, and use Gmail to email them, there is a simple way to create a formula that will open Gmail to a list of conversations with that contact. Toggle a switch with a condition. Learn how to use a conditional formula in a toggle column so that the toggle switches on or off based on the value of data in other columns. Putting longer text in a card. Sometimes long text makes a row too tall and unsightly. It may be better to show long text in its own card and use same-record linking to select the card. Learn how.","title":"Community Highlights"},{"location":"newsletters/2022-01/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2022-01/#inventory-manager","text":"Manage your inventory, and track incoming and outgoing orders. GO TO TEMPLATE","title":"Inventory Manager"},{"location":"newsletters/2022-01/#influencer-outreach","text":"Keep track of interactions and progress in your influencer marketing campaigns. GO TO TEMPLATE","title":"Influencer Outreach"},{"location":"newsletters/2022-01/#exercise-planner","text":"Build workout plans in Grist, and quickly pull them up at the gym while you\u2019re pumping iron. \ud83d\udcaa GO TO TEMPLATE","title":"Exercise Planner"},{"location":"newsletters/2022-01/#software-deals-tracker","text":"If you\u2019re always excited to try the latest SaaS products, then you\u2019ve probably bought many lifetime and annual deals. Track your deals and monitor upcoming expirations with this template. GO TO TEMPLATE","title":"Software Deals Tracker"},{"location":"newsletters/2022-01/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2022-01/#we-are-here-to-support-you","text":"Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"We are here to support you"},{"location":"newsletters/2021-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Zapier Instant Trigger # Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers. Learning Grist # Webinar: Build Highly Productive Layouts # Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING Video: Checking Required Fields # Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO Community Highlights # Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document. New Templates # Habit Tracker # Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE Internal Links Tracker for SEO # Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE UTM Link Builder # Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE Meme Generator # Build memes right in Grist! GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/12"},{"location":"newsletters/2021-12/#december-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill December 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2021 Newsletter"},{"location":"newsletters/2021-12/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-12/#zapier-instant-trigger","text":"Grist\u2019s Zapier integrations have gotten faster! Look for Zapier instant triggers when building your zaps. Learn more about triggers.","title":"Zapier Instant Trigger"},{"location":"newsletters/2021-12/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-12/#webinar-build-highly-productive-layouts","text":"Learn how to go from tables to application-like layouts. We\u2019ll share tips on how to design for efficient workflows and data visualization. Wednesday January 19th at 3:00pm US Eastern Time. SIGN UP FOR JANUARY\u2019S WEBINAR Our December\u2019s webinar addressed how to import and summarize data in Grist. Dmitry shows how to create a Grist structure that pulls insights from your data with summary tables, and then import additional data to the existing structure. WATCH DECEMBER\u2019S RECORDING","title":"Webinar: Build Highly Productive Layouts"},{"location":"newsletters/2021-12/#video-checking-required-fields","text":"Sometimes, certain fields are required, such as the email column of a contacts table. Learn how to use a formula to check if required fields are filled, and modify other formulas to only evaluate records that meet requirements. WATCH VIDEO","title":"Video: Checking Required Fields"},{"location":"newsletters/2021-12/#community-highlights","text":"Create dependent sub-categories. Dependent sub-categories can be useful for organizing data, e.g. vegetables > onion. Learn how to set dependent sub-categories. How to resolve errors due to empty cells. Formulas apply to all rows within a column. Thus when a row is empty, you\u2019ll get an error. It\u2019s correct but unsightly. Here\u2019s how to address it. Keeping a log of events. Sometime it\u2019s useful to keep a time-stamped log of events, such as records\u2019 status changes. Learn how to create one, with a video explanation embedded in the Grist document.","title":"Community Highlights"},{"location":"newsletters/2021-12/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-12/#habit-tracker","text":"Set yourself up for success with your New Year resolutions! Build better habits with this simple weekly habit and progress tracker. GO TO TEMPLATE","title":"Habit Tracker"},{"location":"newsletters/2021-12/#internal-links-tracker-for-seo","text":"Optimize your SEO with an internal links tracker and simple auditing tool to find orphaned pages and most linked-to pages. GO TO TEMPLATE","title":"Internal Links Tracker for SEO"},{"location":"newsletters/2021-12/#utm-link-builder","text":"Easily build and keep track of your marketing campaign\u2019s UTM parameters. GO TO TEMPLATE","title":"UTM Link Builder"},{"location":"newsletters/2021-12/#meme-generator","text":"Build memes right in Grist! GO TO TEMPLATE","title":"Meme Generator"},{"location":"newsletters/2021-12/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Import Column Mapping # When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more. Filter on Hidden Columns # It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b More Sorting Options # There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration. Donut Chart # Grist now supports donut charts! Python 3.9 # Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions. #1 Product of the Day on Product Hunt! # Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING Video: Finding Duplicate Values with a Formula # Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO Community Highlights # Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables. New Templates # Recruiting # Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE Portfolio Performance # Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE Event Speakers # Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"2021/11"},{"location":"newsletters/2021-11/#november-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #42494B; padding: 16px 20px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; color: #FFFFFF; } .header-month { color: #FFFFFF; } .header-welcome { margin-top: 12px; color: #FFFFFF; } Grist for the Mill November 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2021 Newsletter"},{"location":"newsletters/2021-11/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-11/#import-column-mapping","text":"When importing additional data to an existing table in Grist, you can now map columns in your file to columns in the Grist table. Learn more.","title":"Import Column Mapping"},{"location":"newsletters/2021-11/#filter-on-hidden-columns","text":"It is now possible to filter tables based on data in hidden columns. \ud83d\udc7b","title":"Filter on Hidden Columns"},{"location":"newsletters/2021-11/#more-sorting-options","text":"There are now more sorting options available, including natural sort. Learn more about additional sorting options. Note that Choice columns have a unique sorting option based on choice position in the choice configuration.","title":"More Sorting Options"},{"location":"newsletters/2021-11/#donut-chart","text":"Grist now supports donut charts!","title":"Donut Chart"},{"location":"newsletters/2021-11/#python-39","text":"Python 3.9 is the new default for new documents. Compared to previous default (2.7) it has some useful packages such as mathematical statistics functions. Learn more about supported Python versions.","title":"Python 3.9"},{"location":"newsletters/2021-11/#1-product-of-the-day-on-product-hunt","text":"Grist achieved Product of the Day on Product Hunt! \ud83c\udf89\ud83d\ude80\ud83d\udc31\u200d\ud83d\ude80 Thank you for showing your support! Many Grist creators took time out of their day to leave kind reviews and words of encouragement. Thank you! \ud83d\ude4f","title":"#1 Product of the Day on Product Hunt!"},{"location":"newsletters/2021-11/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-11/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn Grist best practices. Wednesday December 15th at 3:00pm US Eastern Time (New York). SIGN UP FOR DECEMBER\u2019S WEBINAR During November\u2019s webinar, Grist VP of Product Anais Concepcion discussed the difference between typical formulas and trigger formulas, and when to use which. She also shared some of her favorite formulas and dove deep into how to use reference columns in lookup formulas. WATCH NOVEMBER\u2019S RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-11/#video-finding-duplicate-values-with-a-formula","text":"Formulas are a great way to find and flag duplicate values in a column. This video breaks down a formula that finds duplicates to deepen your understanding of formulas and Grist. WATCH VIDEO","title":"Video: Finding Duplicate Values with a Formula"},{"location":"newsletters/2021-11/#community-highlights","text":"Split text string into a list. If you import a long string of text, such as keywords separated by commas, it would be useful to break that string into a list of values that may be filtered and grouped in a Choice List column. Learn how. Identifying values that are not in another table. In the video above, the formula finds duplicates (or unique values) in the same column. Learn how to compare values across tables.","title":"Community Highlights"},{"location":"newsletters/2021-11/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-11/#recruiting","text":"Collaboratively track job applicants, and attach resume and interview notes. GO TO TEMPLATE","title":"Recruiting"},{"location":"newsletters/2021-11/#portfolio-performance","text":"Track your portfolio value and performance, including closed and open P&L. GO TO TEMPLATE","title":"Portfolio Performance"},{"location":"newsletters/2021-11/#event-speakers","text":"Track speakers booked for events, and flag events that are not yet fully booked. GO TO TEMPLATE","title":"Event Speakers"},{"location":"newsletters/2021-11/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , share ideas in our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Editing Choices # You can now edit existing choice values and apply those edits to your data automatically! Inline Links # Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs. Preview Changes in Incremental Imports # When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more . Learning Grist # Build with Grist Webinar # Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING Access Rules Video # Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO Community Highlights # Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates. New Templates # Account-based Sales Team # Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE Time Tracking & Invoicing # Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE Expert Witness Database # Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE Cap Table # Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE Doggie Daycare # Manage your daycare business in one place. GO TO TEMPLATE Ceasar Cipher # Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/10"},{"location":"newsletters/2021-10/#october-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2021 Newsletter"},{"location":"newsletters/2021-10/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-10/#editing-choices","text":"You can now edit existing choice values and apply those edits to your data automatically!","title":"Editing Choices"},{"location":"newsletters/2021-10/#inline-links","text":"Text fields now automatically convert URLs into hyperlinks, including in cells that contain both plaintext and URLs.","title":"Inline Links"},{"location":"newsletters/2021-10/#preview-changes-in-incremental-imports","text":"When importing additional data to an existing table, and using a merge key to match duplicate records, you can now preview changes to your data before committing to the import. Learn more .","title":"Preview Changes in Incremental Imports"},{"location":"newsletters/2021-10/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-10/#build-with-grist-webinar","text":"Sign up for November\u2019s Webinar Join Grist experts on a live webinar to learn Grist best practices. Half the webinar is dedicated to a live Q&A where you can get answers to your questions. \ud83d\udca1 The webinar will go live on Thursday, November 18th at 12:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Watch October\u2019s Webinar Recording On October 18th we held our first Build with Grist webinar, focused on best practices to get the most out of Grist. Grist CEO Dmitry Sagalovskiy shared tips on adapting data from a spreadsheet to a database using the DRY principle, and building dashboards using linked widgets. Other topics covered include finding the most recent value from a list of historical records, filtering by several fields, understanding the lookup function and reference lookups, and more. WATCH RECORDING","title":"Build with Grist Webinar"},{"location":"newsletters/2021-10/#access-rules-video","text":"Limiting access for team members Access rules are a beta feature of Grist which make it possible to limit access to parts of your data based on custom rules. The rules can be very granular and grant view or edit permissions based on a number of variables. The most common use of access rules is for teams who want to limit which records team members can see. For example, it is often desirable for sales representatives to only see records related to their owns sales, while their manager can see all records. We cover how to set up those rules in a 5 minute video. WATCH VIDEO","title":"Access Rules Video"},{"location":"newsletters/2021-10/#community-highlights","text":"Overtime Calculator. Automatically calculate and keep track of weekly overtime rates. Calculating terms. Suppose you sell memberships that last 12 months and want to automatically calculate membership expiration dates. Learn how to calculate future dates.","title":"Community Highlights"},{"location":"newsletters/2021-10/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-10/#account-based-sales-team","text":"Track your sales team\u2019s accounts, contacts, and deals. Access rules limit sales reps access to viewing and editing only their deals, contacts, and accounts. GO TO TEMPLATE","title":"Account-based Sales Team"},{"location":"newsletters/2021-10/#time-tracking-invoicing","text":"Quickly record time spent on projects, summarize it, and create invoices. GO TO TEMPLATE","title":"Time Tracking & Invoicing"},{"location":"newsletters/2021-10/#expert-witness-database","text":"Create a simple database of expert witnesses that may be called to testify on the stand. GO TO TEMPLATE","title":"Expert Witness Database"},{"location":"newsletters/2021-10/#cap-table","text":"Keep track of shareholders, issued shares and options, and percentage ownership. GO TO TEMPLATE","title":"Cap Table"},{"location":"newsletters/2021-10/#doggie-daycare","text":"Manage your daycare business in one place. GO TO TEMPLATE","title":"Doggie Daycare"},{"location":"newsletters/2021-10/#ceasar-cipher","text":"Roman Emperor Julius Ceasar used the Ceasar Cipher to encrypt messages. Write a message to encrypt it with this ancient method. GO TO TEMPLATE","title":"Ceasar Cipher"},{"location":"newsletters/2021-10/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Help spread the word?"},{"location":"newsletters/2021-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Improved Incremental Imports # You may now select a merge key when importing more data into an existing table. Integrately and KonnectzIT # In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website . International Currencies # When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings. Learning Grist # Build with Grist Webinar # Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR Are you\u2026Python curious? # There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER Community Highlights # Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not. Help spread the word? # If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius New Templates # Rental Management # Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE Corporate Funding # Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE General Ledger # Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE Sports League Standings # Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE D&D Combat Tracker # Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/09"},{"location":"newsletters/2021-09/#september-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2021 Newsletter"},{"location":"newsletters/2021-09/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-09/#improved-incremental-imports","text":"You may now select a merge key when importing more data into an existing table.","title":"Improved Incremental Imports"},{"location":"newsletters/2021-09/#integrately-and-konnectzit","text":"In addition to Zapier and Pabbly Actions, you may now build integrations with Integrately and KonnectzIT. \ud83d\ude80 Read more on Integrately\u2019s website and KonnectzIT\u2019s website .","title":"Integrately and KonnectzIT"},{"location":"newsletters/2021-09/#international-currencies","text":"When formatting numeric and integer columns, you may now choose from a list of global currencies. You can set the default currency in document settings. Learn more about document settings.","title":"International Currencies"},{"location":"newsletters/2021-09/#learning-grist","text":"","title":"Learning Grist"},{"location":"newsletters/2021-09/#build-with-grist-webinar","text":"Join Grist experts on a live webinar to learn the tips and tricks that\u2019ll help you become a Grist power creator. \ud83d\udcaa The webinar will go live on Monday October 18th at 3:00pm US Eastern Time (New York). SIGN UP FOR WEBINAR","title":"Build with Grist Webinar"},{"location":"newsletters/2021-09/#are-youpython-curious","text":"There are many online resources for learning Python. One of Grist\u2019s developers, Alex Hall, has a side project for learning Python called futurecoder. We recommend it! GO TO FUTURECODER","title":"Are you…Python curious?"},{"location":"newsletters/2021-09/#community-highlights","text":"Extracting months and quarters from dates. If you have a date column, you may want to summarize those dates by month or quarter. Embedding read-only Google Maps. Learn how to embed a Google Map into a Grist document. Creating required fields. It is possible to check if required fields have been filled, or not.","title":"Community Highlights"},{"location":"newsletters/2021-09/#help-spread-the-word","text":"If you\u2019re interested in helping Grist grow, consider leaving a review on product review sites. Here\u2019s short list where your review could make a big impact. Thank you! \ud83d\ude4f G2 Capterra TrustRadius","title":"Help spread the word?"},{"location":"newsletters/2021-09/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-09/#rental-management","text":"Track tenants, leases, and income and expenses related to rental properties in one place. GO TO TEMPLATE","title":"Rental Management"},{"location":"newsletters/2021-09/#corporate-funding","text":"Easily plan and summarize a company\u2019s funding structure. GO TO TEMPLATE","title":"Corporate Funding"},{"location":"newsletters/2021-09/#general-ledger","text":"Build a general ledger of income and expenses related to multiple companies, and summarize data in helpful dashboards. GO TO TEMPLATE","title":"General Ledger"},{"location":"newsletters/2021-09/#sports-league-standings","text":"Track a sports leagues\u2019 matches and automatically generate the season\u2019s standings. GO TO TEMPLATE","title":"Sports League Standings"},{"location":"newsletters/2021-09/#dd-combat-tracker","text":"Dungeon masters, use Grist to easily plan encounters, calculate key figures, and keep track of combat. (Dice roller included.) GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"D&D Combat Tracker"},{"location":"newsletters/2021-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Reference Lists # It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more. Embedding Grist # Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how. Pabbly Integration # You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website. Row-based API # The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more. Edit Subdomain # Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page. Formula Support # Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum Large Template Library # Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES Quick Tips # Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide. New Templates # Restaurant Inventory # Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE Restaurant Custom Orders # Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE Custom Product Builder # Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"2021/08"},{"location":"newsletters/2021-08/#august-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2021 Newsletter"},{"location":"newsletters/2021-08/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-08/#reference-lists","text":"It is now possible to relate a record in one table to multiple records in another table. Choose the Reference List column type to establish a one to many relationship in your document. Learn more.","title":"Reference Lists"},{"location":"newsletters/2021-08/#embedding-grist","text":"Have data you want to share on your website? You may now embed Grist pages as view-only data that updates live. Learn how.","title":"Embedding Grist"},{"location":"newsletters/2021-08/#pabbly-integration","text":"You may now import data to Grist with Pabbly! Read more on Pabbly\u2019s website.","title":"Pabbly Integration"},{"location":"newsletters/2021-08/#row-based-api","text":"The Grist API is now more convenient with row-based (aka record-based) endpoints. Learn more.","title":"Row-based API"},{"location":"newsletters/2021-08/#edit-subdomain","text":"Did you sign up for a team plan, but changed your mind on what subdomain name to use for your site? You may now edit your subdomain from your billing page. Click on your profile icon in the top-right to access your billing page.","title":"Edit Subdomain"},{"location":"newsletters/2021-08/#formula-support","text":"Need help with formulas? There\u2019s a dedicated category in our community forum where you can find formulas for common use cases, and ask for help. Visit the Forum","title":"Formula Support"},{"location":"newsletters/2021-08/#large-template-library","text":"Finding the right template for your project just got easier. Click on Examples & Templates (in the left-side panel of your personal or team site dashboard) to find a larger library of templates to choose from. SEE ALL TEMPLATES","title":"Large Template Library"},{"location":"newsletters/2021-08/#quick-tips","text":"Multiple Accounts. You can sign in with multiple emails into Grist to easily switch between accounts. Click on your profile icon in the top-right, then select \u201cAdd Account\u201d. Finding Duplicates. Find duplicates using the formula: \"DUP\" if len(Products.lookupRecords(ProductCode=$ProductCode)) > 1 else \"\" For more help, follow this guide. Color code conditional values. Use a formula and colorful Choice column entries to flag records that need attention. Follow our quick guide.","title":"Quick Tips"},{"location":"newsletters/2021-08/#new-templates","text":"","title":"New Templates"},{"location":"newsletters/2021-08/#restaurant-inventory","text":"Keep track of your restaurant inventory and supplier information, and create purchase orders right in Grist. GO TO TEMPLATE","title":"Restaurant Inventory"},{"location":"newsletters/2021-08/#restaurant-custom-orders","text":"Build custom orders, calculate ingredient costs, and generate a bill of materials, all in one document. GO TO TEMPLATE","title":"Restaurant Custom Orders"},{"location":"newsletters/2021-08/#custom-product-builder","text":"Build custom products and production contracts that add up component costs and add your profit margin to the final cost. GO TO TEMPLATE Have questions, feedback, or need help? Search our Help Center , watch video tutorials , join our Community , or contact us at support@getgrist.com .","title":"Custom Product Builder"},{"location":"newsletters/2021-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Colors! # Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more. Google Sheets Integration # You can now easily import or export your data to and from Grist and Google Drive. Read more. Automatic User and Time Stamps # Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns. New Resources # Introducing the Grist Community Forum # We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum Visit our Product Roadmap # Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap Quick Tips # Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view. Dig Deeper # Easily Create Automatic User and Time Stamps # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps New Template # Grant Application and Funding Tracker # This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/07"},{"location":"newsletters/2021-07/#july-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2021 Newsletter"},{"location":"newsletters/2021-07/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-07/#colors","text":"Make your Grist document pop with color! Options in Choice and Choice List columns can now be color coded. In addition to making your documents look great and fun, color choices make it easier to scan and glean information quickly. Read more.","title":"Colors!"},{"location":"newsletters/2021-07/#google-sheets-integration","text":"You can now easily import or export your data to and from Grist and Google Drive. Read more.","title":"Google Sheets Integration"},{"location":"newsletters/2021-07/#automatic-user-and-time-stamps","text":"Want to know when a record was updated and by whom? It is now possible to create columns that stamp a user\u2019s name or the time to a record when it is updated or created. Read more about time stamps and authorship stamps . We\u2019ve also created a step-by-step video tutorial that walks you through how to create time and user stamp columns.","title":"Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-resources","text":"","title":"New Resources"},{"location":"newsletters/2021-07/#introducing-the-grist-community-forum","text":"We\u2019ve launched a Grist Community Forum where you can quickly look up solutions, share project ideas, provide feedback, suggest features, and stay up to date on the latest Grist features and announcements. Visit the Forum","title":"Introducing the Grist Community Forum"},{"location":"newsletters/2021-07/#visit-our-product-roadmap","text":"Want to know what we\u2019re working on? Our product roadmap is now public! If you have a Github account, we encourage you to leave comments on roadmap items. We love to hear from you. Visit the Roadmap","title":"Visit our Product Roadmap"},{"location":"newsletters/2021-07/#quick-tips","text":"Reference column best practices. When creating a reference column it is best practice to choose a display column that is a unique identifier for the record. For example, for records of people, it could be full name or email. To show other data, such as department or birthday, pull it in using the formula $[Reference Column Id].[Referenced Record\u2019s Field] , (for example $Person.Department ). You may review how this formula works in our reference column guide . Labeling hyperlinks with text. In hyperlink columns, you can label a link with text by adding a label before the url: [link label] url . The brackets are not needed. Using Search to Find a Card. If you have a card widget on a page and quickly want to find the right card, you can use the search bar on the page to quickly bring the relevant card into view.","title":"Quick Tips"},{"location":"newsletters/2021-07/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-07/#easily-create-automatic-user-and-time-stamps","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. We\u2019ve created a video tutorial that walks you through how to create time and user stamp columns in 3 easy steps. LEARN: User/Time Stamps","title":"Easily Create Automatic User and Time Stamps"},{"location":"newsletters/2021-07/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-07/#grant-application-and-funding-tracker","text":"This template helps NGOs track grant applications that have been submitted to foundations to win grant funding. The template also includes a funding overview which shows funding in the pipeline and funding awarded, broken down by NGO program. Open Grant Tracker Have questions, feedback, or need help? Search our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Grant Application and Funding Tracker"},{"location":"newsletters/2021-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Freeze Columns # You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way. Read-only Editor # Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor Quick Tips # Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla . Dig Deeper # Analyzing Data with Summary Tables and Formulas # Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables New Template # Advanced Timesheet Tracker # The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/06"},{"location":"newsletters/2021-06/#june-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2021 Newsletter"},{"location":"newsletters/2021-06/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-06/#freeze-columns","text":"You can now freeze columns in Grist! Tame those wide spreadsheets and view your data, your way.","title":"Freeze Columns"},{"location":"newsletters/2021-06/#read-only-editor","text":"Team members with view-only access can now open a cell editor to more easily read long text, access attachments, or take a peek at formulas. They will still be prevented from editing the cell, as indicated by the gray lock icon. Read-only cell editor locked for edits v. unrestricted cell editor","title":"Read-only Editor"},{"location":"newsletters/2021-06/#quick-tips","text":"Duplicate Document v Work on a Copy. When making changes to a document\u2019s structure it\u2019s a good practice to Work on a copy . This does not automatically save a duplicate of your document. It creates an unsaved working copy where you can make edits, and then choose to replace the current document, copy your new edits to a new document, or discard your changes altogether. Duplicate Document does create a new, duplicated document, and you can choose whether or not to copy data or retain only the document\u2019s structure. Exporting Backups. Grist periodically takes snapshots of your documents. You can save any version of your document (current or historical snapshots) to backup your data. To backup historical snapshots, click on Document History then Snapshots . Select any snapshot\u2019s menu then click on Open Snapshot . Click on the share icon to open a menu from which you can download the entire document as a Grist file . Combining text fields with formulas. You may sometimes have text in multiple columns that you would like to display in one column. For example, a table may have first name and last name columns. You may easily create a full name column with the formula \"{}, {}\".format($Last_Name, $First_Name) . The part in quotes is a format string: each set of curly brackets ( {} ) in it gets replaced with the next value from parentheses after format . In this case, the full name would be displayed as Benson, Carla .","title":"Quick Tips"},{"location":"newsletters/2021-06/#dig-deeper","text":"","title":"Dig Deeper"},{"location":"newsletters/2021-06/#analyzing-data-with-summary-tables-and-formulas","text":"Summary tables and formulas are vital tools in capturing actionable insights from your data. They make it possible to group records into specific categories, then compute sums using those record groups. Grist makes it easy to create summary tables and work with the $group field in summary formulas. We\u2019ve created a video tutorial that helps you master two key concepts: summary tables and summary formulas. The tutorial guides you into building two summary pages in an advanced timesheet tracker template. LEARN: Summary Tables","title":"Analyzing Data with Summary Tables and Formulas"},{"location":"newsletters/2021-06/#new-template","text":"","title":"New Template"},{"location":"newsletters/2021-06/#advanced-timesheet-tracker","text":"The timesheet tracker makes it easier to track contractors\u2019 timesheets across various months and departments. It has a dashboard where contractors can submit their hours directly into Grist, and see only their historical timesheet data. The company\u2019s payroll department can see everyone\u2019s timesheets, in all accounts, across all months, and have access to summary pages that provide useful data around spending. They can also set permissions to allow or disallow timesheet edits in certain months. The summary tables tutorial starts with an unfinished version of the template, and guides you into creating two new pages. If you want to use the completed template, go to the tutorial solution. Note that there are access rules in place for the completed template which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. See Template Make a copy to become the document owner and see all data and pages. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Advanced Timesheet Tracker"},{"location":"newsletters/2021-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Organizing Data with Reference Columns # Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns . What\u2019s New # Choice Lists # You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one. Search Improvements # When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox. Hyperlinks within Same Document # Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents. Quick Tips # Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/05"},{"location":"newsletters/2021-05/#may-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2021 Newsletter"},{"location":"newsletters/2021-05/#organizing-data-with-reference-columns","text":"Reference columns are at the heart of what Grist does best: make it easy to set up a relational database and create highly productive layouts. If you\u2019re not familiar with databases, understanding reference columns can feel a little tricky. That\u2019s why we\u2019ve created a simple template you can copy and edit to deepen your understanding of reference columns. In our example, you are a job applicant who uses Grist to track your job application status at multiple companies. You would like a simple dashboard that makes it easy to see your job status at a glance, and to add new jobs, interviews and other milestones. Follow our 3 step guide to learn how to use reference columns .","title":"Organizing Data with Reference Columns"},{"location":"newsletters/2021-05/#whats-new","text":"","title":"What’s New"},{"location":"newsletters/2021-05/#choice-lists","text":"You can now select multiple options in a Choice List column. In the screenshot below, multiple dog shows are selected from a choice list, instead of just one.","title":"Choice Lists"},{"location":"newsletters/2021-05/#search-improvements","text":"When searching in Grist, the search now stays on the same page by default. If you want to search across all pages, simply mark the checkbox.","title":"Search Improvements"},{"location":"newsletters/2021-05/#hyperlinks-within-same-document","text":"Previously all hyperlinks opened in a new tab. Now, hyperlinks that point to a different part of the same document will open in the same tab. This makes it easier to jump to relevant parts of large documents.","title":"Hyperlinks within Same Document"},{"location":"newsletters/2021-05/#quick-tips","text":"Adding new record from reference column drop-down list. You may add a new value to the dropdown list in a reference column without switching to the underlying table. Just type in the value you want to add and select the + value in the dropdown list. Grist will automatically add a new record containing this value to the underlying table and insert the proper reference. Edit Card Widget Layout. When converting a table into a card widget, you may want to move fields around to a more intuitive format. Click on the ... menu above the widget you\u2019d like to edit, then select EDIT LAYOUT. Click and drag fields to reposition them, then click SAVE LAYOUT. Card List Widget Themes. The card list widget tiles cards vertically. Grist has three-built in themes to choose from, depending on your need and how you wish to view the data. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-04/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . April 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Understanding Link Sharing # Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view. Creating Unique Link Keys in 4 Steps # The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How What\u2019s New # You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data. Quick Tips # Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"2021/04"},{"location":"newsletters/2021-04/#april-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill April 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"April 2021 Newsletter"},{"location":"newsletters/2021-04/#understanding-link-sharing","text":"Last month we released granular access controls which makes it possible to limit who can see or edit which parts of a document, down to individual columns and rows. This same level of control can be set for documents shared via links with third parties who do not have a Grist account or who are not a part of your organization. For example, you may want to share purchase history with a customer, permit staff to see only their work schedules, or show a client their project\u2019s status. In Grist, \u201clink keys\u201d are URL parameters that when combined with the access control rules will determine which data a link recipient is permitted to view.","title":"Understanding Link Sharing"},{"location":"newsletters/2021-04/#creating-unique-link-keys-in-4-steps","text":"The best way to learn is by doing. We\u2019ve created a simple template you can copy and edit to deepen your understanding of link sharing. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. Follow our 4 step guide to learn how to share limited data via links . The private tutor can see all data, but a parent can only see their family\u2019s data. Learn How","title":"Creating Unique Link Keys in 4 Steps"},{"location":"newsletters/2021-04/#whats-new","text":"You can now create filter buttons in a bar above any view in Grist. The filter bar can be toggled on or off, and edited to only include buttons for those columns you commonly filter by. The bar can also be saved so that everyone with access to the doc can more easily filter data.","title":"What’s New"},{"location":"newsletters/2021-04/#quick-tips","text":"Freezing unique identifiers . When using the Grist\u2019s UUID() function, you may want to freeze values assigned to each record. If you don\u2019t, the unique identifier will generate anew when you re-open the doc. To freeze unique identifiers, convert the column to the data column, and set the default formula for new records to UUID() so that new rows will also be assigned a unique identifier. Creating links in cells . To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under Cell Format. Link Keys and Public Access. If you\u2019re generating unique links to limited views of data, make sure you have turned on Public Access under \u201cManage Users\u201d to allow link-sharing. Viewers who access your document via links will still be limited in accordance with your access rules. Still need help? Visit our Help Center , watch video tutorials , or contact us at support@getgrist.com .","title":"Quick Tips"},{"location":"newsletters/2021-03/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . March 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Access rules # Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help. New Example # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration. Quick Tips # Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc). Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/03"},{"location":"newsletters/2021-03/#march-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill March 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"March 2021 Newsletter"},{"location":"newsletters/2021-03/#access-rules","text":"Grist now offers \u201caccess rules\u201d for fine-grained control of who can see (or edit) which parts of your document at the table, column, and row level. In December, we invited those interested in early access to try the feature out. We\u2019ve made a lot of improvements, and are now excited to open the feature to the public. Things to know about access rules: Until now, everyone you shared a document with saw the same content. Now, their views can be filtered. For example, a table or column may be visible only to certain users. Or some users may see only rows that meet a condition. Until now, all editors and owners of a document could change anything about its content. Now, document owners can control what specific editors can change. For example, a table or column may be locked for certain editors. Or some editors may only update, add, or remove rows that meet a condition. If you need it, editors can be organized into groups or teams using a table within the document itself, and granted permissions systematically. You can make special links to give ad-hoc view access to pieces of the document, including documents shared with the public. Access rules are marked as a Beta feature, meaning that the design of access rules is likely to evolve, and sometimes that will require updates to documents that use them. We won\u2019t make such changes lightly. Grist access rules put great power in your hands. With that power comes great responsibility. Take your time to read our documentation , explore the examples , and test the rules you create . Feel free to reach out to consult us as well, at support@getgrist.com . We\u2019ll be glad to help.","title":"Access rules"},{"location":"newsletters/2021-03/#new-example","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In our Lead List example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors, and show how they can use access rules to shape collaboration.","title":"New Example"},{"location":"newsletters/2021-03/#quick-tips","text":"Cell Color . You can add a splash of color to a column using the Cell Color option for Columns (in tables) or Fields (in cards). The color will apply consistently in all views where cells from that column appear. SQL Queries . Grist documents are also downloadable SQL databases, in SQLite format. If you download your document (via the button) you can query it using the standard sqlite3 tool (available on the SQLite site ) or from just about any language or database tool. Your tables and columns in Grist will show up exactly as SQLite tables and columns. Here\u2019s a query against the Lead List example document: You may not need to do this often, but when you do it is very handy. We\u2019ve recently improved the fit between SQLite column types and Grist column types, so if a database importer fails to recognize a column, try recreating the column or converting it to a more precise type (e.g. Integer, Text, Numeric, Date, DateTime etc).","title":"Quick Tips"},{"location":"newsletters/2021-03/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-02/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . February 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing . What\u2019s New # Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements! Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/02"},{"location":"newsletters/2021-02/#february-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill February 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"February 2021 Newsletter"},{"location":"newsletters/2021-02/#quick-tips","text":"Totals. To compute totals of a column, spreadsheet users will often reach for the last row of their table to enter a formula there. In Grist, every row is a database record, including the last row. But totals are certainly available! Instead of adding a row for totals, add a one-line summary table. Click the \u201cAdd New\u201d button, and select \u201cAdd Widget to Page\u201d. In the dialog that opens, click the summation icon ( ) next to the table you\u2019d like totals for: Click \u201cAdd to Page\u201d, and you\u2019ll see a one-line summary of your table. Use the drag handle just to the left of the title to move it below your table (where you normally expect totals). Grist creates sums of all numeric columns automatically. You can remove any you don\u2019t need, add new ones, or edit ones for which you need something other than sum (perhaps AVERAGE , MAX , or an entirely custom Python calculation). Read Summary tables for more on summary tables and their many uses. Real-time Sharing. Even on the free plan, you can share a document and collaborate on it. Any change that you or another editor makes will immediately show up on the other person\u2019s screen. To share a document, click the sharing icon ( ), and select \u201cManage Users\u201d. In the dialog that opens, type in the email address of the person to invite. Change their role from Viewer to Editor if you\u2019d like to give them editing permission. Then click Confirm. Your collaborator now has access to your document, and Grist will send them an email with a link to it. Read more at Sharing .","title":"Quick Tips"},{"location":"newsletters/2021-02/#whats-new","text":"Grist mobile support has come a long way recently, and we are happy to say that you can now use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar: For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them: To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option: Please report any issues, and share suggestions for improvements!","title":"What’s New"},{"location":"newsletters/2021-02/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2021-01/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . January 2021 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more . New Example # In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video. Find a Consultant, Be a Consultant # Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2021/01"},{"location":"newsletters/2021-01/#january-2021-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; margin-bottom: -32px; display: inline-block; width: 80px; height: 128px; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } .center { text-align: center; } Grist for the Mill January 2021 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"January 2021 Newsletter"},{"location":"newsletters/2021-01/#quick-tips","text":"Entering Formulas. To enter a formula in Grist, simply hit the equal sign ( = ) on the keyboard. To edit an existing formula, hit Enter . Unlike a typical spreadsheet, a Grist formula applies to the whole column, and you can edit this single formula via any of the cells in the column. Read more about formulas . Use Date Picker. If you convert a column to type Date, you\u2019ll be able to enter dates using the date picker interface. To convert a column, open the right-side \u201cColumn\u201d panel, and select \u201cDate\u201d under Column Type: There are options for how to format dates too! See here for more .","title":"Quick Tips"},{"location":"newsletters/2021-01/#new-example","text":"In our Task Management article, we share a document similar to what we use internally at Grist Labs to manage our work. This simple document works better for us than any custom software, thanks to the combination of data linking, dropdowns to assign tasks, easy copy-pasting when needed, and live collaboration. And it works just as well in the all-remote work setup, when all our meetings are over video.","title":"New Example"},{"location":"newsletters/2021-01/#find-a-consultant-be-a-consultant","text":"Get help building. Many businesses have complex data. Feel free to reach out to us for help building your database. A good way is to email us at support@getgrist.com with a description of what you\u2019d like for your database (maybe even sketch some screens on paper and send photos). Then schedule a call . Often that\u2019s enough to get rolling and continue on your own. For bigger projects, or custom integrations, we can recommend a consultant to work with. Help others! Are you a freelancer? Do you enjoy building in Grist and organizing data? We\u2019d be glad to support you, and to add you to our list of independent Grist consultants. Please fill out this interest form: register to be a Grist consultant .","title":"Find a Consultant, Be a Consultant"},{"location":"newsletters/2021-01/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-12/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . December 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. What\u2019s New # Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options. What\u2019s Coming # Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know! New Examples # Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026 Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/12"},{"location":"newsletters/2020-12/#december-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill December 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"December 2020 Newsletter"},{"location":"newsletters/2020-12/#whats-new","text":"Want to check how your document has changed recently? Just press the Document History button in the bottom left of your document, choose a snapshot on the right, click the three-dots symbol ( ) beside it, and select one of the new Compare to Previous or Compare to Current options.","title":"What’s New"},{"location":"newsletters/2020-12/#whats-coming","text":"Grist has brought you a lot of new features in 2020, and it looks like 2021 is going to be another bumper year. One big feature we\u2019re particularly excited about is fine-grained access control, where you\u2019ll be able to determine who can see (or edit) which parts of your document at the table, column, and row level. Several of you have been asking for this, in different ways, and we\u2019re happy to have a solution in hand that will cover a lot of use-cases. If you are interested in getting early access, let us know!","title":"What’s Coming"},{"location":"newsletters/2020-12/#new-examples","text":"Show a Map . This custom widget renders a list of locations as a geographic map. So if you\u2019re making a list of places you want to go once travel is a thing again, you can lay them out and start making plans\u2026","title":"New Examples"},{"location":"newsletters/2020-12/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-11/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . November 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Open Source Announcement # We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code. Quick Tips # Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses. What\u2019s New # Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly. New Examples # Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/11"},{"location":"newsletters/2020-11/#november-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill November 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"November 2020 Newsletter"},{"location":"newsletters/2020-11/#open-source-announcement","text":"We are happy to announce the beta release of the open source version of Grist ! As a modern, powerful spreadsheet, Grist fits well into the open-source ecosystem, and is as friendly to hacker-developers as to hacker-users. Join the discussion at HackerNews . Read the announcement . This will interest technically-minded users who\u2019d like to extend Grist, or those with unusual requirements. Meanwhile, the service you know and love remains the best supported and cost-efficient option, and will benefit from the increased confidence in Grist source code.","title":"Open Source Announcement"},{"location":"newsletters/2020-11/#quick-tips","text":"Add a Record. Some of the simplest shortcuts are the most useful. To add a record, press Ctrl + = (Windows) or \u2318 = (Mac). Press Shift at the same time to insert the record above the cursor rather than below. Cook up a Unique Identifier. Let\u2019s say you\u2019d like to refer to records in a table, but don\u2019t have a column that identifies each record uniquely. Perhaps you have people with First_Name and Last_Name , or sessions with Program_Name and Date . To use such tables as a destination of a Reference column, you\u2019ll need a unique identifier. You can create one using a formula that stitches together the fields that identify a record uniquely. For example, create a column Full_Name with the formula $Last_Name + \", \" + $First_Name or a column Session_Name with the formula \"[%s] %s\" % ($Date, $Program_Name) The latter is Python syntax for formatting strings: each occurrence of %s in the quoted string is replaced by a value from the list in parentheses.","title":"Quick Tips"},{"location":"newsletters/2020-11/#whats-new","text":"Improved Attachments. The preview of attachments has been vastly improved, and supports showing PDF, video, and audio files. Simply double-click a cell with attachments to preview its content: View Differences. You can now view differences between a copy of a document you make using \u201cWork on a Copy\u201d and the original, as well as what changed since a backup snapshot in the document\u2019s history. Find the \u201cCompare\u201d menu item in the Share menu: This feature is experimental for now, but is already quite useful: This supports the workflow of trying out changes , which should be familiar to users of version control systems such as Git. One of its benefits is allowing view-only users to propose changes, even though they cannot edit the document directly.","title":"What\u2019s New"},{"location":"newsletters/2020-11/#new-examples","text":"Treasure Hunt : Even better than using Grist for work is using Grist for fun! Check out this template that helps you plan a treasure hunt without losing track. Conspire with relatives or friends online to create a trail of clues, then watch and laugh as the rest of your household flails their way through it.","title":"New Examples"},{"location":"newsletters/2020-11/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-10/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . October 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0 What\u2019s New # Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below). Open Source Beta # We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source . New Examples # Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/10"},{"location":"newsletters/2020-10/#october-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill October 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"October 2020 Newsletter"},{"location":"newsletters/2020-10/#quick-tips","text":"Fill a Range. To fill a range of cells with the same value, enter the value in the top cell, then select the range below starting with this cell (which you can do by dragging the mouse, or Shift -clicking the bottom cell), then press Ctrl + D (Windows) or \u2318 D (Mac). Remember it as \u201cD\u201d for filling D own. The usual Copy-Paste shortcuts work too \u2013 Ctrl + C Ctrl + V (Windows) or \u2318 C \u2318 V (Mac). Pasting a value into a larger range will fill the range with multiple copies of the value. Conditional Expressions. If you have a formula like $Birthday.year , you\u2019ll find that when the Birthday column is empty, the formula shows an error. It\u2019s easy to avoid it using Python\u2019s conditional expressions: $Birthday.year if $Birthday else None The same idea can help in other situations, e.g. to avoid a ZeroDivisionError: $Total / $Count if $Count else 0","title":"Quick Tips"},{"location":"newsletters/2020-10/#whats-new","text":"Printing. You can now easily print any widget on your screen, whether a table of data, a card, or a custom widget. Click the three-dots icon on top of the widget, and select \u201cPrint Widget\u201d: Printing also works for special-purpose widgets such as Invoices or Mailing Labels (see below).","title":"What\u2019s New"},{"location":"newsletters/2020-10/#open-source-beta","text":"We are excited to announce that we are taking Grist open-source! The open-source version should be considered in Beta for now, but eager developers are welcome to check it out: https://github.com/gristlabs/grist-core . The secure managed service you know and love remains unchanged, and will be all the stronger from engagement with the open-source community. Placing our code in the public domain also brings added trust in quality of code and in long-term availability of Grist. Read more at Why Open Source .","title":"Open Source Beta"},{"location":"newsletters/2020-10/#new-examples","text":"Print Mailing Labels . This custom widget supports popular label sizes and makes it easy to create printable labels and have them available at the click of a button. It\u2019s yet another example of the extensibility of Grist.","title":"New Examples"},{"location":"newsletters/2020-10/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-09/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . September 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email . What\u2019s New # Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation. New Examples # Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/09"},{"location":"newsletters/2020-09/#september-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill September 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"September 2020 Newsletter"},{"location":"newsletters/2020-09/#quick-tips","text":"Keyboard Shortcuts. Grist supports many keyboard shortcuts, including quite a few from Excel and Google Sheets. These are listed here , and also available within Grist by pressing F1 or \u2318 / (Mac) or Ctrl + / (Windows). Referenced Columns. With the cursor anywhere in a column of type Reference (used to store a pointer to another record), the Column Options panel on the right has a section called \u201cAdd Referenced Columns\u201d. It allows quickly adding any field from the linked record. If you hit Enter on the added column, you\u2019ll notice that it is simply a formula column, such as $Contact.Email .","title":"Quick Tips"},{"location":"newsletters/2020-09/#whats-new","text":"Public Sharing. You can now share your documents publicly. In the \u201cShare\u201d menu ( ) on top, select \u201cManage Users\u201d. Then toggle the dropdown next to \u201cPublic Access\u201d to \u201cOn\u201d: Once you confirm the change, anyone with the link to your document will be able to view it. They don\u2019t even need to have a Grist login. The \u201cCopy Link\u201d button is handy to copy the link to the clipboard for pasting into an email, tweet, or anywhere else. You can also allow anyone with the link to your document to edit it: simply switch the role in the Public Access row to Editor. Note that this allows anyone with the link to change absolutely anything in your document, including deleting all the data. The snapshots in Document History should help if anything goes wrong. Improved Formula Suggestions. While typing in formulas, Grist is now more helpful in suggesting common functions, and includes their parameters, and a link to documentation.","title":"What\u2019s New"},{"location":"newsletters/2020-09/#new-examples","text":"Here is an example of what you can do with Grist: Tracking Payroll : This template is convenient for small businesses, especially those with part-time employees. Grist can help you keep track of employee hours, rates, and roles, and to reduce mistakes with up-to-date payroll summaries and detailed histories of rates and hours.","title":"New Examples"},{"location":"newsletters/2020-09/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-08/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . August 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically. What\u2019s New # Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ). New Examples # Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/08"},{"location":"newsletters/2020-08/#august-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill August 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"August 2020 Newsletter"},{"location":"newsletters/2020-08/#quick-tips","text":"Date Offsets. It is easy to get into muddles calculating dates. DATEADD is an easy and reliable way to add years, months, weeks, and days to a date. For example, to calculate three months on from a specific date, do DATEADD($date, months=3) . For a week and a day before that, do DATEADD($date, months=3, weeks=-1, day=-1) . Simple! Column Names. Sometimes, there is a tension between choosing a column name as a verbose label to explain what it is about, and choosing something short and snappy that is comfortable in formulas. You can have both! Just open the column options and uncheck \u201c Use Name as ID? \u201d. Then you can set the name used as a label and the name used in formulas separately. And as always with Grist, if you change your mind later you can change or relink the names and all your formulas will update automatically.","title":"Quick Tips"},{"location":"newsletters/2020-08/#whats-new","text":"Sort your documents by modification date. You can now choose to order your documents so that the most recently changed ones show up first, using the new By Name / By Date Modified option at the top right of document lists. List documents as icons. You may now also control whether documents show as a grid of icons or as a list of names, using the new grid and stack icons at the top right of document lists. Reactive custom widgets. We have made it simple for web developers to create Custom Widgets that offer new reactive views of your data that update automatically. A developer need only call grist.onRecord(s) to subscribe to a record or a set of records linked to the widget; after that, presentation is a matter of preparing HTML/CSS/JS assets (no Grist-specific knowledge needed). The user of the widget controls data selection using regular Widget Linking . We invite web developers to have a go at building new widgets or visualizations, and we\u2019d be excited to hear about the results (shoot us a mail at support@getgrist.com ).","title":"What\u2019s New"},{"location":"newsletters/2020-08/#new-examples","text":"Here is an example of what you can do with Grist: Preparing Invoices : Preview printable invoices side by side with your client and order data. Enter billable items quickly, making use of formulas, and see the invoice update immediately just like any other part of a spreadsheet. This is also an example of the kind of new visualization that can be built with Custom Widgets .","title":"New Examples"},{"location":"newsletters/2020-08/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-07/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . July 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this. What\u2019s New # More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps. New Examples # Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas. Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"2020/07"},{"location":"newsletters/2020-07/#july-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill July 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"July 2020 Newsletter"},{"location":"newsletters/2020-07/#quick-tips","text":"Review Document Logic. The \u201cCode View\u201d link in the left panel shows a Python module that describes the full data model of your document: all tables, all columns with types, and all formulas. It\u2019s a great overview of the logic that drives a document\u2019s calculations. Links to Compose Emails. If you have a column like \u201cEmail\u201d, try adding a formula column with the formula \"mailto:\" + $Email , set its type to Text , and CELL FORMAT to HyperLink . You can now click a link to compose an email to this address. See New Examples below for much more you can do with this.","title":"Quick Tips"},{"location":"newsletters/2020-07/#whats-new","text":"More number format options . One-click formatting for dollar amounts, percentages, showing negative numbers in parentheses, and more: Longer backup retention. Grist makes regular automatic backups of your documents as you work on them. It now retains them for longer, even on the free plan. Recover or purge deleted documents. Deleted documents are moved to Trash. It is now easy to recover them. Documents in Trash get deleted permanently after 30 days, or you can delete them permanently yourself to purge them from our systems sooner. Use your own domain. If you use Grist to share data with your own customers and would like it served from your own domain, this option is now available as an add-on to the Team Plan . Zapier integration. Grist integration on Zapier is now available in beta. Go ahead and connect Grist to other apps.","title":"What\u2019s New"},{"location":"newsletters/2020-07/#new-examples","text":"Here is an example of what you can do with Grist: Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. You can essentially create email templates using Grist formulas.","title":"New Examples"},{"location":"newsletters/2020-07/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com .","title":"Learning Grist"},{"location":"newsletters/2020-06/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . June 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links. What\u2019s New # Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document. New Examples # Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Grist Overview Demo # Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 . Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"2020/06"},{"location":"newsletters/2020-06/#june-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill June 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"June 2020 Newsletter"},{"location":"newsletters/2020-06/#quick-tips","text":"Get the Calendar Quarter. If you have a column of type Date, you can extract parts of it with Excel-like functions for year , month , and more . But how to get the quarter? This formula will produce values like \u201c2020 Q2\u201d: \"%s Q%s\" % ($MyDate.year, CEILING($MyDate.month, 3) / 3) Make Hyperlinks. To create a column of links in Grist, set the column type to Text, and click the \u201cHyperLink\u201d button under CELL FORMAT: Now, a value like Grist Labs https://getgrist.com will show a link to https://getgrist.com with \u201cGrist Labs\u201d as the text. See below for a Book Club example that uses formulas extensively to generate useful links.","title":"Quick Tips"},{"location":"newsletters/2020-06/#whats-new","text":"Safely work on complex changes. As your document or spreadsheet grows in importance, the ease of making changes may get overshadowed by the fear of breaking something. Grist now has a tool to help. Click \u201cDuplicate Document\u201d 1 in the Share menu: You\u2019ll get an unsaved copy of your document. You can experiment on it without fear of affecting the original. Make changes big or small, one or many. Once satisfied, open the dropdown next to \u201cSave Copy\u201d and click \u201cReplace Original\u201d: Grist will warn you if the replacement risks overwriting any recent changes in the original. Examine and restore backups. Grist automatically saves backups of your documents as you work on them. You can now view them easily. Click \u201cDocument History\u201d in the left panel, then click the \u201cSnapshots\u201d tab in the panel on the right: You can open any of the listed snapshots. You can revert to an old version using the \u201cReplace Original\u201d option 2 , or save an old version as a new document.","title":"What\u2019s New"},{"location":"newsletters/2020-06/#new-examples","text":"Here are some examples of what you can do with Grist: Slice and Dice Credit Card Expenses : Grist offers handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like.","title":"New Examples"},{"location":"newsletters/2020-06/#grist-overview-demo","text":"Interested in seeing an overview of Grist features on a realistic example? Check out our new demo video: https://www.youtube.com/watch?v=XYZ_ZGSxU00 .","title":"Grist Overview Demo"},{"location":"newsletters/2020-06/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series . Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click the Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, use the \u201cWork on a Copy\u201d option for this functionality. See Copying Documents . \u21a9 This menu was updated in July 2020, see Automatic Backups . \u21a9","title":"Learning Grist"},{"location":"newsletters/2020-05/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . May 2020 Newsletter # /* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users. Quick Tips # Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here . What\u2019s New # Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more. Grist @ New York Tech Meetup # We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q Learning Grist # Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"2020/05"},{"location":"newsletters/2020-05/#may-2020-newsletter","text":"/* restore some poorly overridden defaults */ .newsletter-header .table { background-color: initial; border: initial; } .newsletter-header .table > tbody > tr > td { padding: initial; border: initial; vertical-align: initial; } .newsletter-header img.header-img { padding: initial; max-width: initial; display: initial; padding: initial; line-height: initial; background-color: initial; border: initial; border-radius: initial; margin: initial; } /* copy newsletter styles, with a prefix for sufficient specificity */ .newsletter-header .header { border: none; padding: 0; margin: 0; } .newsletter-header table > tbody > tr > td.header-image { width: 80px; padding-right: 16px; } .newsletter-header table > tbody > tr > td.header-text { background-color: #c4ffcd; padding: 16px 36px; } .newsletter-header table.header-top { border: none; padding: 0; margin: 0; width: 100%; } .header-title { font-family: Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 24px; line-height: 28px; } .header-month { } .header-welcome { margin-top: 12px; color: #666666; } Grist for the Mill May 2020 \u2022 getgrist.com Welcome to our monthly newsletter of updates and tips for Grist users.","title":"May 2020 Newsletter"},{"location":"newsletters/2020-05/#quick-tips","text":"Copy as Template. Want to use your carefully crafted document with all new data? Click the \u201cShare\u201d menu on top ( ), and select \u201cCopy as Template\u201d 1 . The copy will have all the structure, formulas, and layouts, but none of the data. Link to a specific cell. Select a cell and press \u2318 \u21e7 A (Mac) or Ctrl + Shift + A (Windows). Share the copied link to have your recipient open your document to the same selected cell. More here .","title":"Quick Tips"},{"location":"newsletters/2020-05/#whats-new","text":"Grist is being constantly improved. Here are some of the more recent changes: New Grist documents have better-looking URLs , and include the document title. Now when you see a Grist link, it\u2019s easy to remember which document it\u2019s for. Reference columns now offer a more helpful auto-complete . To set a Reference value, start to type, and see suggestions from the linked table ranked by best match. Charts have a new \u201cLog Scale Y-axis\u201d option, to show data on a log scale , which is important in many data science applications. Super-user? If you are itching to hack, there is now an experimental way to add your very own components to Grist. See Custom page widget for more.","title":"What\u2019s New"},{"location":"newsletters/2020-05/#grist-new-york-tech-meetup","text":"We presented a nice demo at the NY Tech Meetup back in February. We finally have a video of our demo and Q&A to share with you: https://www.youtube.com/watch?v=a2RlRGRnr9Q","title":"Grist @ New York Tech Meetup"},{"location":"newsletters/2020-05/#learning-grist","text":"Get started quickly with basic Grist concepts by watching this playlist of a few very short introductory videos: Grist Video Series Each of our featured Examples & Templates has a related tutorial that shows step-by-step how to build it from scratch. Read through one to gain a deeper understanding of how various features play together. Visit our Help Center to find all of the above, along with the full product documentation. Questions or suggestions? Click Give Feedback link near the bottom left in the Grist application, or simply email support@getgrist.com . After the July 2020 update, select \u201cDuplicate Document\u201d, and mark the \u201cAs Template\u201d checkbox in the dialog that opens. See Copying Documents . \u21a9","title":"Learning Grist"},{"location":"examples/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . More Examples # Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data. Have something to share? # Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"More examples"},{"location":"examples/#more-examples","text":"Here we will post useful examples of what you can do with Grist, sometimes with ready-to-use templates. Slicing and Dicing Credit Card Expenses : Grist offers a handy way to explore your credit card transactions quickly. American Express card members can use the ready-made template in the example. Book Lists with Library and Store Look-ups : Grist\u2019s link handling and formulas can help you with your book club, by adding automatic links to libraries, stores, review sites, and other references. The same ideas could work with Ikea furniture, food orders, whatever you like. Prepare Emails using Formulas : Turn cells into links that open an email program to compose a new message, and prefill its recipients, subject, and more. Preparing Invoices : Preview printable invoices side by side with your client data. Tracking Payroll : Keep track of employee rates, roles, and hours conveniently, with up-to-date reliable payroll summaries, and detailed history. Print Mailing Labels : Print mailing labels easily. A custom widget supporting popular label sizes makes it quick to generate labels and print from Grist. Treasure Hunt : Plan a treasure hunt without losing track. Brainstorm clues, then assemble them into a consistent trail. Map : Show locations listed in a table on a map. Task Management for Teams : This glorified To-Do list \u2013 similar to the one we use internally at Grist labs \u2013 works well to manage a team\u2019s tasks. Lead List : A very simple list of leads, assigned to individuals to follow up, with control of assignments reserved for document owners. Understanding Link Keys : Learn how to create unique links that share limited views of your Grist document. Reference Columns Guide : Mastering Reference Columns in 3 Steps Summary Tables Guide : Master Summary Tables with 2 Concepts, using the example of a timesheet tracker, with detailed account tracking. Auto Time and User Stamps : Learn to create columns that stamp the time or a user\u2019s name to a record when it is created or updated. Access Rules to Restrict Duplicate Records : Learn to how to create a condition in Access Rules to restrict a duplicate record from being created. Creating Proposals & Contracts : Preview printable forms side by side with your project data.","title":"More Examples"},{"location":"examples/#have-something-to-share","text":"Have your own template to share with the world? Email us at support@getgrist.com , and we may feature it in a future post.","title":"Have something to share?"},{"location":"examples/2020-06-credit-card/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Slicing and Dicing Expenses # Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Credit card expenses"},{"location":"examples/2020-06-credit-card/#slicing-and-dicing-expenses","text":"Grist offers a handy way to explore your credit card transactions quickly, for example if you want to see transactions by: Category Card Member Month Combination of any of the above Here is an example using data from American Express (redacted for privacy, of course). See below for using it as a template for your own data. This first page is a dashboard: it shows a few summary charts \u2014 your expenses by month, and two pie charts to let you quickly see which card member or which category has the most expenses. The next three pages show breakdowns by Category, Card Member, and by Month. In addition to the totals by Category, you can click on any Category to see all transactions in it, and then click on any transaction to see its complete details. Here is the example with sample data that you can play with. To use it for your own data, start by downloading your transactions. For American Express credit cards: Log in at https://americanexpress.com Go to \u201cStatements & Activity\u201d tab, then select \u201cView By Year\u201d or \u201cCustom Date Range\u201d. Click \u201cDownload your Transactions\u201d icon. When asked to choose the download format, select \u201cCSV\u201d and select the checkbox that says \u201cInclude all additional transaction details\u201d. To import this data into Grist: Open the template here . Click \u201cAdd New\u201d button and choose \u201cImport from file\u201d. In the dialog that shows, change \u201cTo\u201d table from \u201cNew Table\u201d to \u201cActivity\u201d, like so: Click \u201cImport\u201d, and you are done. Your data is ready to explore. Note When you start with a template link, your copy of the document is initially unsaved. To keep this data for later, click \u201cSave Copy\u201d, and give the document a name. You\u2019ll see the document later on your Grist home page at https://docs.getgrist.com . A template like this doesn\u2019t take long to prepare. It uses a combination of summary tables and widget linking . Have feedback or improvements to contribute? Please share with us by emailing support@getgrist.com .","title":"Slicing and Dicing Expenses"},{"location":"examples/2020-06-book-club/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Book Lists with Library and Store Look-ups # If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too: Library and store lookups # Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out: Ready-made template # Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Book club links"},{"location":"examples/2020-06-book-club/#book-lists-with-library-and-store-look-ups","text":"If there\u2019s one thing better than reading a book, it is reading with friends. To organize a book club is pretty simple. The club will work best if everyone actually gets the book, and has opinions for what to read next time. Grist can help with all that. To nudge the book into everyone\u2019s hands, it helps to have links to borrow the book at your local library, and to buy the book from your local (or not-so-local) store. And for ideas about what to read next time, it helps to be able to easily suggest books, and to learn about books others suggest via sites like GoodReads, Wikipedia, and Amazon. Suppose we start with a simple table of books, with book titles and author names. To borrow or buy the book, it is best to have its unique ISBN code, so there\u2019s no ambiguity or mix-ups. One thing we can do is add a link to search for a book on isbnsearch.org by its title and author. To do this, add a new column, and then set it to be a HyperLink column : What we\u2019re going to do is fill this column using a formula that takes the book title and the author\u2019s name, and uses them as a keyword to search on the isbnsearch.org site. Here\u2019s the formula: import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") \"[isbn] https://isbnsearch.org/search?s=\" + urllib.quote(keywords) URLs with spaces or apostrophes or other odd letters you might find in names need special encoding, so we\u2019ve used the python urllib module to make sure everything gets encoded correctly. Now we\u2019ve got a handy [isbn] link beside each book: We can click the arrow by [isbn] , locate the book, and make note of its ISBN code in a new column: Then we can add it into our book list. Now that we have a helping hand for finding ISBNs, let\u2019s add a few more books too:","title":"Book Lists with Library and Store Look-ups"},{"location":"examples/2020-06-book-club/#library-and-store-lookups","text":"Once we have the ISBN, adding a link to buy the book is easy. Here\u2019s a formula for the indiebound.org site, which in the U.S. is likely to have an independent book store near you as a member: \"[buy] https://indiebound.org/book/\" + $ISBN If you don\u2019t have a local bookstore, there\u2019s Amazon, or pretty much any site you like (just find a search page on their site, and match the pattern). import urllib keywords = ($Title or \"\") + \" \" + ($Author or \"\") + \" \" + ($ISBN or \"\") \"[amazon] https://www.amazon.com/s?i=stripbooks&k=\" + urllib.quote($keywords) The new links look like this: Clicking on the [buy] link near a book now brings us to that book on indiebound.org . For your local library, the same idea is very likely to work. For example, in northern New Jersey, in the US, here\u2019s what you want: import urllib prefix = \"[borrow] https://catalog.bccls.org/polaris/search/searchresults.aspx?ctx=placeholder&type=Keyword&by=ISBN&term=\" prefix + urllib.quote($ISBN) And here\u2019s how to look up Goodreads to see what people think of a book: \"[opinion] https://www.goodreads.com/search?q=\" + $ISBN And Wikipedia to start a deep dive: import urllib keywords = ($Title or \"\") + \" \" + ($Author.Name or \"\") \"[wikipedia] https://en.wikipedia.org/wiki/Special:Search/\" + urllib.quote(keywords) Once we have all these links, it makes sense to add a Card View so we can lay them out:","title":"Library and store lookups"},{"location":"examples/2020-06-book-club/#ready-made-template","text":"Here is an example book list that you can play with. The actual books listed may not be your thing, of course! Adjust to taste.","title":"Ready-made template"},{"location":"examples/2020-07-email-compose/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Prepare Emails using Formulas # You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas. Simple Mailto Links # The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose . Cc, Bcc, Subject, Body # In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose . Emailing Multiple People # Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient. Configuring Email Program # If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Prefill emails"},{"location":"examples/2020-07-email-compose/#prepare-emails-using-formulas","text":"You may already know that you can add hyperlink fields in Grist. You may also know about \u201cmailto\u201d links that open a mail program to create a new email message. Less well-known is that \u201cmailto\u201d links allow pre-filling many parts of the email message. If you use Grist to store contacts, you can essentially create email templates using Grist formulas.","title":"Prepare Emails using Formulas"},{"location":"examples/2020-07-email-compose/#simple-mailto-links","text":"The simplest \u201cmailto\u201d link in Grist looks like mailto:someone@example.com . When the column is set to Text, and its format is set to Hyperlink, it shows as someone@example.com . If you have a table with columns Full_Name and Email , add another column with this formula: \"Compose Email mailto:%s\" % ($Email) . Set its type to Text, and its cell format to Hyperlink: You\u2019ll get a link in each person\u2019s row, which you can click to start composing an email to that person: See an example of this in action here: Simple Compose .","title":"Simple Mailto Links"},{"location":"examples/2020-07-email-compose/#cc-bcc-subject-body","text":"In addition to opening your email program and filling in the \u201cTO\u201d field, a \u201cmailto\u201d link can fill in other parts of the email, using this format: mailto:?cc=&bcc=&reply-to=&subject=&body= All fields are optional. You can specify multiple email addresses for To/Cc/Bcc lists by separating them with commas. Values of each field should be percent-encoded , which in Python can be done using urllib.parse.quote . To put this together, this formula will produce a hyperlink to create a pre-filled email: from urllib.parse import quote return \"Compose mailto:%s?cc=sales@example.com&subject=%s&body=%s\" % ( quote($Email), quote($Subject), quote($Body)) A live example of this is here: Advanced Compose .","title":"Cc, Bcc, Subject, Body"},{"location":"examples/2020-07-email-compose/#emailing-multiple-people","text":"Email links are very handy when emailing a group of people, such as students in a class, or people on a certain project. For instance, if you have a Reference column \u201cProject\u201d that ties a person to a project, then in the table of projects, you can look up all associated people using lookupRecords . You can then build a link to email them as a group: from urllib.parse import quote people = People.lookupRecords(Project=$id) return \"Email Group mailto:%s\" % quote(\", \".join(people.Email)) You can see this formula at work in Group Compose . Don\u2019t use this to replace an email marketing platform: since emails use your regular email program, you shouldn\u2019t use it for emailing thousands of people. But for small groups, this can be very convenient.","title":"Emailing Multiple People"},{"location":"examples/2020-07-email-compose/#configuring-email-program","text":"If your \u201cmailto\u201d links don\u2019t work, or don\u2019t open your preferred email program, here is an article to help you configure it: Changing the default email program . If you need more details, here are some more links: To open a desktop program (such as Mail, Thunderbird, etc.) on a Mac: Instructions . To open a desktop program (such as Outlook, etc. ) on Windows: Instructions . To open Gmail in Chrome and other browsers: Instructions . To open webmail (such as Gmail or Yahoo! Mail) or a desktop program in Firefox: Instructions .","title":"Configuring Email Program"},{"location":"examples/2020-08-invoices/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Preparing invoices # If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there. Setting up an invoice table # First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options . Entering client information # Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section. Entering invoicer information # We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget. Entering item information # Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done! Final polish # You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Prepare invoices"},{"location":"examples/2020-08-invoices/#preparing-invoices","text":"If you are keeping records of who is charged what in Grist, it can be convenient to generate invoices right there and then beside those records. This tutorial shows you how to set up a document like this: You can find a finished template at https://templates.getgrist.com/9NH6D58FmxwP/Invoicing . If you\u2019d like to add invoices to an existing document, making them work the way you want, understanding this tutorial should get you there.","title":"Preparing invoices"},{"location":"examples/2020-08-invoices/#setting-up-an-invoice-table","text":"First of all, make a table to record invoices by creating an empty document and renaming Table1 to Invoices : Now, let\u2019s add a widget beside the table to view the finished invoices. There are all sorts of possible styles of invoice, so Grist lets web developers create their own using Custom Widgets. We\u2019ll use an example invoice style published by Grist, but if it doesn\u2019t match your needs any web developer could tweak it for you. Click on Add New , then Add Widget to Page . Then: For Select Widget choose Custom since we\u2019ll be using a Custom Widget. For Select Data choose Invoices since we\u2019ll be viewing data from the invoices table. For Select By choose INVOICES so that the Custom Widget will show data from whatever invoice is currently selected by the user. Great, now we have two widgets, a Table Widget that will have invoice data, and a Custom Widget that will view that data as a nicely formatted, printable invoice. The Custom Widget starts off blank; select Widget options to specify what we want in it: In the CUSTOM settings section where it says Full URL of webpage to show , put this link: https://gristlabs.github.io/grist-widget/invoices/ And set Access to read table . This grants the Custom Widget access to read the Invoices table. Now place anything you like in a row of the Invoices table (I just added the number 1 ). An incomplete invoice will immediately show up: The incomplete invoice shows what column names to use to control what parts of the invoice. An extra black box shows what columns the widget understands, what columns it is ignoring, and what extra columns the widget could make use of. Currently it is saying that it doesn\u2019t recognize any of the columns present, is expecting columns like Number , Client , Items , etc., and is ignoring the default A , B , and C columns in the Invoices table. Looking at the start of the invoice we see a space for an invoice number, INVOICE NUMBER: #Number . So let\u2019s rename the A column to Number , we could leave the value as 1, but then it looks like we haven\u2019t done much business yet. I\u2019m going to choose the set the invoice number to be $id + 51371 , where $id is an auto-incrementing numeric identifier assigned to each row. But you could set it manually or with a different formula. As soon as it is set, the invoice updates: The next column the help box suggests is Client , so let\u2019s rename the B column to Client . Later we will give client information in a structured way, but for now let\u2019s just put some text here (use Shift + Enter to insert line breaks): Great, now the Client box is filled out. The next suggested column is Items , so let\u2019s rename the C column to Items . Later we will give item information in a structured way, but for now let\u2019s just put some text here: And now the Description is set. We\u2019re missing a total, so let\u2019s add a column called Total and set it to 100 : The invoice won\u2019t update immediately. This is the first new column we\u2019ve added - until now we\u2019ve been renaming them. When we created the Custom Widget, it was given access to the columns that existed at the time of creation. To let the widget see the new column, open Widget options again and move Total from Hidden Columns to Visible Columns : Great, the invoice updated. Now let\u2019s set who the invoice is from, by adding an Invoicer column (remember to make it visible to the widget via Widget options ). As a last step to creating a usable invoice, let\u2019s make an Issued column and put today\u2019s date in it (remember to make it visible to the widget via Widget options ). As soon as the invoice has a date, the black help box will disappear: Okay! If someone sent me that, I\u2019d pay it. You should nudge me by giving it a due date though. Let\u2019s make a Due column and set it to a month from the Issued date. Remember to make the Due column visible to the widget via Widget options . Also, be sure to set the Column Type for Issued to Date or you won\u2019t be able to do date math on it (it will just be a string). If there are special instructions to go with the invoice, we can add a Note column. Remember to make it visible to the widget via Widget options .","title":"Setting up an invoice table"},{"location":"examples/2020-08-invoices/#entering-client-information","text":"Now, let\u2019s make two useful changes to the invoice set-up: Put client information in a separate table, so we don\u2019t have to reenter their address every time we invoice them (and we can import the addresses in bulk). Enter items and prices in a table, so multi-item invoices are easy to make (and we can use formulas and look-ups for pricing if we like). Before doing so, we need to let Grist know that the content of the invoice will depend on these other tables, so that it can update it when something changes, and make sure the invoice gets access to everything it needs. Make a column named References and give it this formula: = RECORD(rec, expand_refs=1) That says \u201ctake the current record, and package it up, including everything it references directly\u201d. Since Grist is a spreadsheet, it also implies \u201cupdate everything that depends on this column if anything in the references changes\u201d. Remember to make the References column visible to the widget via Widget options , so that the widget will get updated as we add and change referenced material. When the invoice widget sees a column named \u201cReferences\u201d, it fills out the invoice using the \u201cpackaged\u201d values in that column, rather than the individual invoice fields. The benefit will be seen in the next step, since these packaged values can include data from related records. Next let\u2019s place client information in a separate table. Add a new table to the page for entering business information by clicking on Add New , Add Widget to Page , then Select Widget > Table and Select Data > New Table : Then rename the table to Businesses . Let\u2019s also empty the Client column so we can see help about what the widget expects there: The widget suggests Name , Street1 , Street2 , City , State and Zip columns. So let\u2019s provide those columns in our Businesses table, and fill them in for an example client. Then place the same Name in the Client column, and in Column options set the Column Type to Reference . Refer to Businesses Name if Grist doesn\u2019t automatically guess that. Once you hit Apply , you will see a nicely formatted Client section.","title":"Entering client information"},{"location":"examples/2020-08-invoices/#entering-invoicer-information","text":"We could do the same thing for the Invoicer column as we did for the Client column, and make it a reference to the Businesses table or a separate table. However, if you will always be using the same name and address for your business, you could skip setting up a reference by entering a formula like this into the Invoicer column (start typing with = to make it a formula): { \"Name\": \"Thunderous Applause\", \"Street1\": \"6502 Automated Rd\", \"City\": \"New York\", \"State\": \"NY\", \"Zip\": \"10003\", \"Email\": \"applause@thunder.com\", \"Phone\": \"+1-800-YAY-YAYS\", \"Website\": \"applause.com\" } Then set the Column Type for Invoicer to Any in the right-side panel. Notice how emails, phone numbers, and links are specially formatted by the widget.","title":"Entering invoicer information"},{"location":"examples/2020-08-invoices/#entering-item-information","text":"Now, let\u2019s set up the list of items and prices that is at the heart of an invoice. Clear the Items column to see what we can put there. It will show that Items can be a list, where each item has a Description , Price , Quantity , and Total . So we go ahead and add an Items table like we added Businesses , and give it those four columns. We can set Total to be this simple formula: $Price * $Quantity Now we need to pull these items into the Invoices table so that the Custom Widget gets access to them. Set the Items column to the formula Items.lookupRecords() , and then reset its column type to be Any . This formula needs a little more work, which we\u2019ll do soon, but let\u2019s just start with that. Great, our invoice is updating nicely! Remove the Total column to get an automatically calculated one: It is probably more comfortable to edit invoices as a Card Widget than a Table Widget, so let\u2019s change that using Widget options , Table , Change Widget , Card , Save . You can customize the card layout to your taste. To add a new invoice, click the little + above the invoices card, set an Issued date, and pick either the existing Client or add a new one. Once we have a second invoice, it becomes clear we skimped on the formula for collecting invoice items - all the invoices contain all the items. No problem, we can get more specific by adding an Invoice column to Items and setting it up to refer to specific Invoices : Once that is done we can sprinkle on some Grist fairy dust, and go to Widget options for the ITEMS Table Widget, click Change Widget , and set SELECT BY to INVOICES Card . Once that is saved, only the items for the currently selected invoice are shown. Even better, when you add new items, the invoice column is automatically set to the invoice you are viewing. So you can just hide the invoice column and forget about it. Now let the Custom Widget know what items to use by updating the Items formula to be more picky: Items.lookupRecords(Invoice=$id) With that, entering new invoices is a breeze! Click + to add a new invoice card. Pick the client, set the issue date. Add items. Done!","title":"Entering item information"},{"location":"examples/2020-08-invoices/#final-polish","text":"You can adjust the set-up to taste. For example, I would choose to add new clients on a separate page ( B or Businesses on the left panel) since that is relatively infrequent; you could choose to keep that on the same page. I don\u2019t need deductions or taxes, if you do you could add columns and/or formulas for those. The invoice custom widget works for me as is, but if I needed to tweak anything I could copy the GitHub repository it is in and change it a bit (or hire a web developer to do that for me - they don\u2019t need to be Grist experts). For interested developers, the GitHub code is here: https://github.com/gristlabs/grist-widget/tree/master/invoices . Enjoy, and good luck getting paid!","title":"Final polish"},{"location":"examples/2020-09-payroll/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Tracking Payroll # If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees. The Payroll Template # The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches. The \u201cPeople\u201d Page # Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference. The \u201cPayroll\u201d Page # To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below. The \u201cPay Periods\u201d Page # Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker. Under the hood # I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments. Using the Payroll template # To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Track payroll"},{"location":"examples/2020-09-payroll/#tracking-payroll","text":"If you have employees, then you are keeping track of payroll. You may be using a payroll service, and it probably asks you to enter hours for each employee once a month. This is where Grist can help you keep track of things conveniently, and to reduce mistakes, especially if you have part-time employees.","title":"Tracking Payroll"},{"location":"examples/2020-09-payroll/#the-payroll-template","text":"The finished template is here: https://templates.getgrist.com/5pHLanQNThxk/Payroll . Feel free to open it in a separate window, and try things yourself as you go along. The template addresses a few needs: We need to be able to add employees, and keep basic contact info for them. We need to set rates at which people are paid, and these rates can change over time. In our example, rates also vary by role: the same person could work as an Instructor in one program, and as a Coordinator in another, at different rates. We need to enter hours for each person. We need to get the monthly totals, both to enter data into our payroll service, and to verify that everything matches.","title":"The Payroll Template"},{"location":"examples/2020-09-payroll/#the-people-page","text":"Let\u2019s start with the \u201cPeople\u201d tab in the example. It addresses our needs (1), (2), and (3). On top, we have a list of people, with contact info such as address and phone number. To add a new person, click into the last row of the PEOPLE table, and type in the data. Below that we have a card for the selected person \u2013 a handy way to view or update the selected person\u2019s record. Next to that are the rates for this person. We keep track of all rates, not only the latest one, along with each rate\u2019s effective date. To add a new rate, select a person, click into the last row of the RATES table, and enter the effective date into the Rate Start column (perhaps using the shortcut for today\u2019s date: \u2318 + ; (semicolon) on Mac or Ctrl + ; on Windows). Set the Role that it applies to, and the Hourly Rate . The Comment field is for your own notes. Don\u2019t modify values for existing rates, or past payroll computations will be affected. When you give someone a raise, add it as a new row. This way, full history is preserved, and is available for reference.","title":"The “People” Page"},{"location":"examples/2020-09-payroll/#the-payroll-page","text":"To enter payroll hours, switch to the \u201cPayroll\u201d page. It is the most common task, so we placed this page first in the list of pages to have it open by default when you open the document. Scroll to the bottom (you can use these handy shortcuts: \u2318 \u2193 on Mac, Ctrl + \u2193 on Windows). To add a payroll entry, type in the date (or use the date picker), select the person using auto-complete, select their role, and type in their hours. The next few columns automatically look up the relevant rate, and calculate the payment. If you\u2019d like to enter hours for a range of dates, e.g. for a particular week, or for someone working full-time this month, you can use Date as the start date, and enter also an End Date . The interesting bit about this page is how the Per Hour rate is looked up. You don\u2019t need to care if you are just using the template, but if you enjoy looking under the hood, or if you need to customize this further to your needs, I\u2019ll explain it in detail below.","title":"The “Payroll” Page"},{"location":"examples/2020-09-payroll/#the-pay-periods-page","text":"Here the totals for each period are automatically tallied up to address our need (5) \u2014 to see an overview, to enter data into a payroll system, and to check that everything matches. The latest pay period (in our case, a month) is on top of the top-left table. You can select any pay period there to see a pie chart with an overview of pay broken down by role, and a table of how much each person earned that month. The names and hours in the last table are typically all that a payroll service needs. In addition, we included a Dates column summarizing dates worked that month: for people working part time, it helps remember when the income was earned. It could be a handy note to include into a pay stub if your payroll service allows it. That\u2019s it! We now have a convenient and simple payroll tracker.","title":"The “Pay Periods” Page"},{"location":"examples/2020-09-payroll/#under-the-hood","text":"I promised to show how rates are looked up. Open up the \u201cPayroll\u201d page, click on any cell in the Per Hour column, and hit Enter . The formula you see is an involved Python function, but it is intentionally broken up into bits, with comments explaining what each line does: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. current_rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return current_rate.Hourly_Rate (If this seems too complicated, good luck trying to achieve the same in a traditional spreadsheet!) Read the comments in that code to understand the steps. For those who know Python, it may already make perfect sense. For those who don\u2019t, here are the relevant links to Grist and Python documentation: lookupRecords : how we look up all rates for the given person and role. list comprehensions : how we filter for only those rates with an earlier Rate_Start . max function : how we choose the rate record with the latest Rate_Start . .Field : how we get the Hourly_Rate field from the rate record. This formula is also a good illustration of how helpful it is to have Python available, along with multi-line formulas, variables, and comments.","title":"Under the hood"},{"location":"examples/2020-09-payroll/#using-the-payroll-template","text":"To start using this example for your own payroll, start with the template: GO TO TEMPLATE then click \u201cSave Copy\u201d: Then mark the \u201cAs Template\u201d checkbox. Your copy will then include all the structure, logic, and layouts of the Payroll document with none of the sample data.","title":"Using the Payroll template"},{"location":"examples/2020-10-print-labels/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Printing Mailing Labels # Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button. Ready-made Template # Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do. Labels for a table of addresses # That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below): A sheet of labels for the same address # If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include. A filtered list of labels # There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE Adding Labels to Your Document # If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes. Add the LabelText formula # Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record. Add the Custom Widget # Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it. Set Preferred Label Size # The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page. Printing Notes # The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off. Further Customization # This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Print mailing labels"},{"location":"examples/2020-10-print-labels/#printing-mailing-labels","text":"Do you ever print mailing labels? Typically, it involves a few steps, first exporting addresses, then importing them into Word or other software that can lay them out for a particular type of label. If you maintain addresses in Grist \u2013 or use labels for name tags, storage containers, child belongings, or other creative uses \u2013 you can add a custom widget to have your printable labels available at the click of a button.","title":"Printing Mailing Labels"},{"location":"examples/2020-10-print-labels/#ready-made-template","text":"Let me first share a template. Later I\u2019ll describe how to add the same functionality to an existing document, e.g. to add mailing labels alongside CRM or Payroll data. The template is at: https://templates.getgrist.com/9nNr9uQwoXWA/Print-Mailing-Labels . You can use it right away. Simply type in your own addresses. The template shows a few variations of what you can do.","title":"Ready-made Template"},{"location":"examples/2020-10-print-labels/#labels-for-a-table-of-addresses","text":"That\u2019s what you see in the first page: You can use the dropdown on top of the labels widget to select the size of the labels, depending on what actual label paper you are planning to print on. What\u2019s actually printed is a hidden column named LabelText . If you show this column, or find it in the \u201cAddresses\u201d page (where it\u2019s visible), you\u2019ll see that it\u2019s a formula that stitches together all the parts of an address. (We get into the details of this formula below.) To print the labels, select \u201cPrint widget\u201d from the menu above the labels widget (see also Printing Notes below):","title":"Labels for a table of addresses"},{"location":"examples/2020-10-print-labels/#a-sheet-of-labels-for-the-same-address","text":"If you need many identical labels, e.g. for a return address, add another column named LabelCount . This is illustrated in the page \u201cLabel Sheets\u201d. If you have multiple addresses in the table, and want to print just one, simply set the count to zero for the addresses you don\u2019t want to include.","title":"A sheet of labels for the same address"},{"location":"examples/2020-10-print-labels/#a-filtered-list-of-labels","text":"There are a few ways in which you can print out only a few labels out of a larger table, and these are illustrated in the linked template: The \u201cAddress Labels\u201d page includes a filter button for State , which allows simple filtering by state. You can click + button to easily create additional filters like this: To print out only a few labels out of a larger table, you can use the LabelCount column with a formula. This formula can produce 1 for a label that should be included, or 0 for a label that should not. The page \u201cFiltered By Formula\u201d shows an example, with the simple formula that includes only California addresses: IF($State == \"CA\", 1, 0) : The \u201cFiltered Manually\u201d page includes a Select for Print column. Click any toggle to include the corresponding row into the label sheet. GO TO TEMPLATE","title":"A filtered list of labels"},{"location":"examples/2020-10-print-labels/#adding-labels-to-your-document","text":"If you have a document with addresses, you can add a page with mailing labels, and the labels will be there \u2013 up-to-date and waiting for you \u2013 any time you open it. I\u2019ll assume that you already have a page that shows a table with addresses. Perhaps it\u2019s called \u201cPeople\u201d, \u201cClients\u201d, or \u201cEmployees\u201d. Open the menu next to the page name in the left panel, and click \u201cDuplicate\u201d: This gives you a new page. We\u2019ll add the labels here, to avoid taking up screen space in the page you used for other purposes.","title":"Adding Labels to Your Document"},{"location":"examples/2020-10-print-labels/#add-the-labeltext-formula","text":"Next, add a column to the table with addresses, and name it LabelText . Type a formula into it that produces an address formatted into multiple lines. Here is an example, but you may need to rename, add, or remove fields depending on the names of your columns: \"%s\\n%s %s\\n%s, %s %s\" % ($Name, $Street1, $Street2, $City, $State, $Zip) Note that this is Python syntax for formatting strings. Here is a brief guide to decode this: Each %s gets replaced with the next value from the parenthesized list after % . \\n inserts a newline. $Name etc. are Grist variables corresponding to fields of the current record.","title":"Add the LabelText formula"},{"location":"examples/2020-10-print-labels/#add-the-custom-widget","text":"Click Add New button and select Add Widget to Page . In the dialog that appears select widget Custom and the table that contains the addresses: Click Add to Page . Open the menu above the widget and select \u201cWidget Options\u201d: In the right-side panel that opens, in the \u201cCustom\u201d section, enter the URL of the widget that implements the mailing labels functionality: https://gristlabs.github.io/grist-widget/printlabels/ Then change the \u201cAccess\u201d dropdown from none to read table . You should now see your labels! If not, check that LabelText is listed below under \u201cVisible Columns\u201d. (If it\u2019s under \u201cHidden Columns\u201d, click the \u201ceye\u201d icon to make it visible to the widget.) You can add another column named LabelCount for printing multiple labels or omitting some labels. If you do, just be sure to add it to \u201cVisible Columns\u201d of the Custom Widget, as those are the only columns available to it.","title":"Add the Custom Widget"},{"location":"examples/2020-10-print-labels/#set-preferred-label-size","text":"The labels widget remembers the last label size you picked. If you\u2019d like it instead to open to a fixed size every time, you can add the preferred size in the widget URL (in the right-side panel), e.g.: https://gristlabs.github.io/grist-widget/printlabels/#labels10 The available sizes are labels8 , labels10 , labels20 , labels30 , labels60 , and labels80 , each of which corresponds to a standard type of label paper (all based on US Letter paper size). In the example document, this is illustrated in the widget on the \u201cFiltered\u201d page.","title":"Set Preferred Label Size"},{"location":"examples/2020-10-print-labels/#printing-notes","text":"The widget uses the browser for printing. To match the exact sizes of the label paper, double-check that you don\u2019t have any settings that would change the scaling, and check Print Preview before printing. If there is a scale set, it should be \u201c100%\u201d, and options such as \u201cIgnore Scaling and Shrink to Fit Page Width\u201d should be off.","title":"Printing Notes"},{"location":"examples/2020-10-print-labels/#further-customization","text":"This feature is built using Custom Widgets . It allows you, or a third-party developer, to customize it further using HTML, CSS, and Javascript. For interested developers, the code for this widget is available at https://github.com/gristlabs/grist-widget/tree/master/printlabels .","title":"Further Customization"},{"location":"examples/2020-11-treasure-hunt/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Planning a Treasure Hunt # A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d. Places # First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet. Clues # Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are. The Trail # Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice. Printing # When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Treasure hunt"},{"location":"examples/2020-11-treasure-hunt/#planning-a-treasure-hunt","text":"A treasure hunt is a fun party diversion that goes like this: All the players (the \u201cseekers\u201d) listen to a clue for a location, then set out to find it. When seekers find the location, they discover another clue there to read. Each clue leads to a new location, until finally instead of a clue the seeker finds a treasure! Seekers can be paired up in teams as needed, depending on their age. The hunt really rewards preparation, and Grist can help with that. Here\u2019s an example hunt: You can play with the example yourself here: https://templates.getgrist.com/ihsZTnKTF7Lr/Treasure-Hunt Once ready to start your own completely fresh hunt just do \u201cSave Copy\u201d and select \u201cAs Template\u201d.","title":"Planning a Treasure Hunt"},{"location":"examples/2020-11-treasure-hunt/#places","text":"First, think of places to send the seekers. You\u2019re looking for places which have some special character or usage, and where there are some obvious nooks or crannies to hide a clue. The set of places should cover as much space as possible within your territory. It is fine to pick places that are close together, as long as someone searching for a clue around one of them wouldn\u2019t stray to the other. I personally take a certain sadistic pleasure in making seekers retrace their steps across the longest distance as often as possible. For example, in an apartment, posters and paintings can be good to hide clues behind, or visually distinctive books on a book shelf. For other spots like the kitchen sink or towel racks, you have to consider whether the clues will survive long enough to be found! Enter everywhere you think of in the \u201cPlaces\u201d widget in the Treasure Hunt document. Don\u2019t worry about their order just yet.","title":"Places"},{"location":"examples/2020-11-treasure-hunt/#clues","text":"Now that you\u2019ve thought of some places, brainstorm clues for them. In the Treasure Hunt document, click on a place that you\u2019ve added, and then try to think of at least one clue for it. Put those clues in the \u201cClues\u201d widget. This can be a good time to share the document around, since you never know who in your family is going to have the twisted mind it takes to come up with the quirkiest clues. Or maybe you do know. There\u2019s no need to restrict yourself to people who are going to be present. Especially this COVID-hit year, it can be a pleasure for people who know your space from the before-times but cannot be physically present to at least visualize it and propose devious hidey-holes to torture those who are.","title":"Clues"},{"location":"examples/2020-11-treasure-hunt/#the-trail","text":"Now that you\u2019ve got places and clues, all that is left is to assemble them into a sequential trail. You can do this in the \u201cTrail\u201d widget. Just start typing in the clue you want to start with, use the autocomplete to confirm it. Then move on to the next clue, in any order you like. Pick one clue per location. Grist will show where each clue needs to be hidden to make the sequence work, which is definitely not rocket science but still surprisingly easy to lose track of (especially for younger trail creators). It will also warn you if you\u2019ve repeated the same location twice.","title":"The Trail"},{"location":"examples/2020-11-treasure-hunt/#printing","text":"When your trail is ready, the document has a \u201cPrint\u201d page where you can print out the clues ready to hide, and the list of where to hide them if delegating this task to enthusiastic helpers. If the trail is outdoors, and it may rain, it is a good idea to wrap clues in aluminum foil or plastic. Ideas for places and clues are good to hang onto for future events, either as a Grist document or as a print-out. Enjoy the hunt, and if you come up with improvements to the spreadsheet please do let us know!","title":"Printing"},{"location":"examples/2020-12-map/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Adding a Map # It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Map"},{"location":"examples/2020-12-map/#adding-a-map","text":"It can be useful sometimes to visualize lists of locations on a geographic map. Grist can do that if a table has at least a Name , Latitude , and Longitude column. Suppose we have a list of some of the largest objects in America: The first step to making a map for these is to add a Custom Widget to the page: Once we set the widget to be https://gristlabs.github.io/grist-widget/map/ and give it Read table access, a map should appear: Clicking around in the list of objects brings up the corresponding \u201cpop-up\u201d on the map. We can add other sections to taste, such as a detail \u201ccard\u201d view of the attraction\u2019s details. This example is available at https://public.getgrist.com/3boQPJrgFmFi/Large-in-America/m/fork . This example has some hidden columns related to geocoding (the process of automatically calculating coordinates from addresses). You can see these on the second page called Geocoding . They are Geocode , Address , and GeocodedAddress . If you would like to use geocoding, you\u2019ll need these three columns, and to grant the map widget full access to your document so it can store results. Address should hold exactly what you want to pass to the geocoder. In the example, it is simply \", \".join([$City, $State]) . The GeocodedAddress should be left untouched; it is a space for the geocoder to store information. Finally, Geocode is a toggle that should be turned on to automatically generate latitude and longitudes. The geocoder will not touch any rows with Geocode turned off. When Geocode is turned on for a row, latitudes and longitudes are computed and stored automatically for that row using Nominatim . Nominatim\u2019s Usage Policy allows for limited, non-bulk creative use. Robust geocoding generally requires an account with an external service and a secret API key. We expect to soon have a convenient way to store secrets in a document, and will then be able to support flexible geocoding options. If you are interested in previewing this feature, please get in touch! Finally, if you know some HTML/CSS/JS, or have a web developer on your team, you\u2019ll find the map custom widget simple to restyle as you like. If you need a hand, just let us know.","title":"Adding a Map"},{"location":"examples/2021-01-tasks/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Task Management for Teams # I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us. Our Workflow # We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . Structure # The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone. My Tasks # The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it. Check-ins # These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog. Backlog # Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews. Task Management Document # The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task management"},{"location":"examples/2021-01-tasks/#task-management-for-teams","text":"I used to be surprised to hear that many people prefer a spreadsheet over project management software. Why? Because it\u2019s simpler. Well, I agree! For the last year, we\u2019ve been using Grist internally to manage projects and tasks within Grist Labs. It feels dead simple, and it works great for us.","title":"Task Management for Teams"},{"location":"examples/2021-01-tasks/#our-workflow","text":"We have a small team, and regular scheduled check-ins. The goals for the check-ins are to go over all the work that was assigned, and to end up with a list of new assignments. After the check-in, everyone can see exactly what\u2019s expected of them for the next time. You can explore the example at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork .","title":"Our Workflow"},{"location":"examples/2021-01-tasks/#structure","text":"The structure is simple. There are three tables: People , i.e. the team members, Check-Ins , identified by date, Tasks , each associated with a person and a check-in. We have one special \u201cperson\u201d named \u201cBacklog\u201d. That\u2019s our way of recording tasks that are not yet assigned to anyone.","title":"Structure"},{"location":"examples/2021-01-tasks/#my-tasks","text":"The page called Main shows all the check-ins, ordered with the latest one on top. When that one is selected, the TASKS table shows all the currently assigned tasks, sorted by person. That\u2019s where I look to remember what\u2019s next, and to have the pleasure of checking off a task as done whenever I\u2019m done with it.","title":"My Tasks"},{"location":"examples/2021-01-tasks/#check-ins","text":"These days everyone is working remotely, so the check-ins are over Zoom. Everyone opens the \u201cCheck-Ins\u201d Grist document, and one person takes charge of making updates, and shares their screen for others to follow. First, create a record for today\u2019s check-in: click into the CHECK_INS table, and hit Ctrl + = ( \u2318 = on Mac) to add a new record, then Ctrl + ; ( \u2318 + ; on Mac) to insert today\u2019s date into it. Then click the second date (previous check-in) to go over previously assigned work. This is where we take turns going over the finished tasks. It\u2019s a chance to mark things as complete. If a task wasn\u2019t started, change the associated date to today\u2019s date \u2013 this will move the task. There is a field to record optional notes about the outcome. If a task was only partly done, we make a note of what got finished, mark it as complete, and make a new task in today\u2019s check-in for the remaining work. Any follow-up tasks are also created for today\u2019s check-in. By the end of it, all tasks still associated with the last check-in are marked as done. It\u2019s a satisfying record of everyone\u2019s work! Now, click the check-in for today. Any tasks that were moved, or follow-up tasks created will be here. This is a chance to create and assign new tasks, and to revisit the backlog.","title":"Check-ins"},{"location":"examples/2021-01-tasks/#backlog","text":"Whenever a new task comes up (say a bug that needs fixing), anyone can add it to the latest check-in. It can be assigned to a person immediately, but if it\u2019s not urgent, it can be assigned to \u201cBacklog\u201d. During check-in, we go over any new backlog items and assign priority for them: just a number. There is a separate Backlog page to view all the backlog tasks, ordered by priority from highest to lowest. When assigning tasks during check-in, visit this page to see if there is anything high-priority that should be assigned. If anyone has spare bandwidth, there are usually plenty of smaller low-priority items that can be picked off as well. That\u2019s about all. The last page we use is called By Person , and it\u2019s just a helpful way to see all tasks completed and pending for any given person. It is a useful reference for quarterly reviews.","title":"Backlog"},{"location":"examples/2021-01-tasks/#task-management-document","text":"The example document is at https://public.getgrist.com/hik1whAV5snj/Task-Management/m/fork . It feels no more complicated than a To-Do list, and that\u2019s the point! To start using it for your own tasks, open the Share menu ( ), and click \u201cDuplicate Document\u2026\u201d. Give it a name and click the \u201cAs Template\u201d checkbox. Your copy will then include the structure and layouts with none of the sample data. Enjoy! May your tasks get done on time!","title":"Task Management Document"},{"location":"examples/2021-03-leads/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . A lead table, with assignments # Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect. Per-user access # Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Lead list"},{"location":"examples/2021-03-leads/#a-lead-table-with-assignments","text":"Access rules let you control how a shared document is used, and maintain a separation of roles and responsibilities. In this example, we suppose an entrepeneur is using Grist as an ad-hoc lead tracker for sales and potential advisors: Our entrepeneur wants to delegate sales leads to a small team of freelancers, and keep potential advisors private. They want to be able to assign someone to follow-up a lead just by writing their initials in an \u201cAssignment\u201d column, and not allow anyone else to change that assignment. Here are the rules they come up with, which you can see yourself by visiting the example : There are three blocks of rules. First, the rule for the Advisors table: This restricts all access to the Advisors table to the owner. We assume the document is owned by the entrepeneur, and shared with anyone else as an editor. Next, there are rules for the Leads table: We allow only the owner to update anything in the Name or Assignment column. We forbid anyone other than the owner from deleting a row of the Leads table. Then there are default rules, not specific to any table: We forbid anyone other than the owner from modifying the document\u2019s structure. The remaining rules here are just the default behavior of any document. There are also two special check-box rules, that would typically be turned off. They give special permission to view access rules and copy this document even if one normally wouldn\u2019t be allowed. These are useful exceptions for an example document with no sensitive information. To play with the document, it is best to make a copy and become its owner. Click \u201cSave Copy\u201d, and place the document in your Personal area or in a team site. Share the document with someone else as an editor and make sure they see what you expect . You\u2019ll want to turn off the \u201cSpecial Rules\u201d that allowed you to inspect the example and to make a complete copy of it: Remember to hit \u201cSave\u201d after you change rules, so they take effect.","title":"A lead table, with assignments"},{"location":"examples/2021-03-leads/#per-user-access","text":"Suppose we now want to restrict the leads to be viewed only by the person they are assigned to (or by the owner). We can do that in several ways. One convenient way is to add a table listing team members, including their email address: Once we have such a table, we can make the Assignment column refer to it. See https://public.getgrist.com/vuPduz2UdJDi/Lead-list-with-team/m/fork for the complete example (you\u2019ll need to make a copy to get full access, otherwise you\u2019ll see a filtered view). We can then use the team members in rules, via a user attribute table : Then every user the document is shared with (other than owners) will see a filtered view of just those leads assigned to them: Read our introduction to access rules to learn more about what you can do with access rules.","title":"Per-user access"},{"location":"examples/2021-04-link-keys/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Create Unique Links in 4 Steps # In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data. Step 1: Create a unique identifier # In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier. Step 2: Connect UUID to records in other tables # In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column. Step 3: Create unique links # In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . Step 4: Create access rules # Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Link keys guide"},{"location":"examples/2021-04-link-keys/#create-unique-links-in-4-steps","text":"In Grist, \u201clink keys\u201d are URL parameters that when combined with the user.LinkKey variable in access rules will determine which data a link recipient is permitted to view. You can learn to do this in four easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. In our example, you are a private tutor who uses Grist to track hours, payments, and client data. You would like to share session and payment history with parents via a link that only shows their family\u2019s data. A simple way to do that would be to set access rules that limit a link recipient\u2019s view to just those records tied to their family. Let\u2019s do that now. The private tutor can see all data, but a parent can only see their family\u2019s data.","title":"Create Unique Links in 4 Steps"},{"location":"examples/2021-04-link-keys/#step-1-create-a-unique-identifier","text":"In the families table, create a new column in which you will use Grist\u2019s UUID() function to generate and assign a unique key to each family. Convert the column to the data column to freeze its values. You\u2019ll notice our formula has changed to a trigger formula. Select \u2018Apply to new records\u2019. This will ensure that new rows will also be assigned a unique identifier.","title":"Step 1: Create a unique identifier"},{"location":"examples/2021-04-link-keys/#step-2-connect-uuid-to-records-in-other-tables","text":"In the Students, Sessions, and Payments tables, add a column that ties each record to the referenced family\u2019s UUID. Name these columns \u201cUUID\u201d, with the simple formula $Family.UUID . Not sure how this works? Brush up on Grist\u2019s powerful reference columns . Tip: The formula $Family.UUID gets the UUID from the record that is referenced in the Family column.","title":"Step 2: Connect UUID to records in other tables"},{"location":"examples/2021-04-link-keys/#step-3-create-unique-links","text":"In the Families table, create a new column in which you will use Grist\u2019s SELF_HYPERLINK() function to generate hyperlinks. Use the formula SELF_HYPERLINK(LinkKey_UUID=$UUID) to create a link key called \u201cUUID\u201d that sets the URL parameter to a specific $UUID within a record. Convert the column type to Text > Hyperlink. How does this work? The link generated for \u201cRaddon, Fin\u201d is .../Private-Tutor-recUUID/p/9?UUID_=6752c258-443d-4a2c-800d-1491da265b72 . The \u201clink key\u201d is the part of the URL that reads ?UUID_=6752c258-443d-4a2c-800d-1491da265b72 .","title":"Step 3: Create unique links"},{"location":"examples/2021-04-link-keys/#step-4-create-access-rules","text":"Open the Access Rules page from the left panel, and create rules to give limited access to your clients. Let\u2019s think about who should access each table, and which parts of it should be accessible. You, the owner of the document should have full access to Read (R), Update (U), Create (C) and Delete (D) records in each table. Add the rule user.Access in [OWNER] to each table to grant owners full access. Why user.Access ? Review access rule conditions to learn more. Parents viewing the document should have Read-Only access just to those records related to their family. In previous steps, we created a unique identifier (UUID) for each family, connected relevant records in all tables to a UUID, and generated URLs with link keys that include those UUIDs. Now we must create access rules that match UUIDs and URL link keys. To do so, add the rule user.LinkKey.UUID == rec.UUID to each table . This tells Grist to look at the URL\u2019s link key (named UUID) and match it to records that include that same UUID. Set access to Read-Only by clicking on the drop-down menu next to \u201cpermissions\u201d. Make sure Public Access is turned on in the \u201cManage Users\u201d panel (see Sharing ). Tip: Do not edit the default rules. Row-level access is granted in the relevant tables. You did it! This is just the beginning. There\u2019s a lot more you can do with link keys. Check out another example to deepen your understanding of link keys even more. Still need help? View the tutorial solution here . Make a copy to see all data:","title":"Step 4: Create access rules"},{"location":"examples/2021-05-reference-columns/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Reference Columns Guide # Mastering Reference Columns in 3 Steps # In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide. Using Reference Columns to Organize Related Data # In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together. Step 1: Creating References # Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table . Converting Columns with Text into Reference Columns # If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells. Creating Reference Columns # In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value. Step 2: Look up additional data in the referenced record # Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns . Step 3: Create a Highly Productive Layout with Linked Tables # One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution . Dig Deeper: Combining formulas and reference columns. # If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Reference columns guide"},{"location":"examples/2021-05-reference-columns/#reference-columns-guide","text":"","title":"Reference Columns Guide"},{"location":"examples/2021-05-reference-columns/#mastering-reference-columns-in-3-steps","text":"In Grist, reference columns are the key tool to organize relational data. Reference columns tell Grist that two separate tables are related, and specifies which records within those tables are related. For example, if you have a table of Dogs and a table of Dog Owners , you may want each dog\u2019s record to explicitly reference their owner\u2019s record in Dog Owners . Reference columns are a powerful tool for looking up data from a related table, and for creating highly productive layouts. You can learn to do this in three easy steps. We\u2019ve created a simple template you can copy and edit as you follow along with this guide.","title":"Mastering Reference Columns in 3 Steps"},{"location":"examples/2021-05-reference-columns/#using-reference-columns-to-organize-related-data","text":"In our example, you are a graphic designer applying for jobs, and you are using Grist to keep track of your job application process. In the template you have four tables: Job Applications , Milestones , Tasks , and Contacts . Your goal is to relate relevant data between tables and create a custom dashboard where you can easily view each job application\u2019s status, and add new job applications and new milestones easily. In this dashboard , clicking on each job application shows only the milestones related to that job application. We can create this relationship using reference columns. Let\u2019s create this dashboard together.","title":"Using Reference Columns to Organize Related Data"},{"location":"examples/2021-05-reference-columns/#step-1-creating-references","text":"Reference columns are a type of column available under COLUMN TYPE. It is helpful to think of the table with the reference column as the referencing table , and the table which is being referenced as the underlying table .","title":"Step 1: Creating References"},{"location":"examples/2021-05-reference-columns/#converting-columns-with-text-into-reference-columns","text":"If you already have text in the selected column, set the COLUMN TYPE in the right-side panel to \u201cReference\u201d. Grist will guess the underlying table and column you want to show in the reference column. You can review and edit this guess and set the appropriate underlying table and display column. DATA FROM TABLE points to the underlying table. SHOW COLUMN sets the display value in the reference column. For example, in the Milestones table, convert the Role column into a reference column. DATA FROM TABLE should point to Job Applications . SHOW COLUMN should be set to Role . Note that the reference column is referencing the entire record and you are choosing which column to display in the reference column. Tip: You can easily identify reference columns by the chain link icons in the column\u2019s cells.","title":"Converting Columns with Text into Reference Columns"},{"location":"examples/2021-05-reference-columns/#creating-reference-columns","text":"In the Contacts table we have a list of contacts that are not associated with any job application or company. Let\u2019s create a new column called Company , set its type to Reference, and point to Job Applications as the underlying table with Company as the display in the reference column. Click on the empty cell to open a drop-down menu and manually select the company at which each contact works. Look at the email address for a hint. Note: You can always click on a Reference Column cell to open the drop-down menu and select a new value.","title":"Creating Reference Columns"},{"location":"examples/2021-05-reference-columns/#step-2-look-up-additional-data-in-the-referenced-record","text":"Recall that the reference column is referencing another table, and correlating two specific records. Although you see a specific column in the display of the reference column, the reference is being made to the entire record . This allows us to look up additional data fields in the related record using a simple formula. Let\u2019s try it. In step 1, in the Milestones table we created a reference column called Role . It would be useful to also see for each Milestone record, the relevant company. For example, in row 1 we see the Milestone event \u201cRejected!\u201d for the role \u201cHead of Digital Design\u201d. It is not immediately apparent which company this is. Let\u2019s use the Role reference column to easily look up the company listed in the \u201cHead of Digital Design\u201d record in the Job Applications table. To do so we create a column called Company and we use the formula $Role.Company . The formula structure is $[Reference Column ID in Referencing Table].[Column ID in Underlying Table] . Grist will also auto-complete parts of the formula as you type it. There\u2019s an alternative way to add multiple columns from an underlying table. If you\u2019re interested in learning more, visit our website\u2019s help section on reference columns .","title":"Step 2: Look up additional data in the referenced record"},{"location":"examples/2021-05-reference-columns/#step-3-create-a-highly-productive-layout-with-linked-tables","text":"One of the most powerful features of Grist is the ability to link related tables in the same page to create highly productive layouts. In the final dashboard shown at the start of this tutorial, we saw that clicking on a job application would populate a view of milestones related to that job application. Let\u2019s do that now by adding Milestones as a widget to the Job Applications page. ( Brush up on widgets here .) Adding the table as a Card List widget makes the data easier to view. Similarly, you may want to change the Job Applications table to a Card List widget. In the CARD LIST menu on the right, select DATA to set data selection rules. Under SELECT BY you will see the option \u201cJOB_APPLICATIONS Card List\u201d. This option is only available because in step 1 we created a reference from the Milestones table to the Job Applications table in the Role reference column. This reference tells Grist which milestones are related to which job applications. Congrats! You now know how to use reference columns to organize related data, give your data structure, and create linked widgets in productive layouts. If you\u2019d like, compare your document to the tutorial solution .","title":"Step 3: Create a Highly Productive Layout with Linked Tables"},{"location":"examples/2021-05-reference-columns/#dig-deeper-combining-formulas-and-reference-columns","text":"If you\u2019re comfortable with formulas, try using formulas in reference columns to make Grist an even more powerful tool. In the tutorial solution , we\u2019ve used a formula to do more. The formula in the Last Milestone field in the Job Applications widget is looking up the most recent date in related records in the Milestones table. Thus, adding a new milestone with a more recent date would automatically update this field. You can learn more about lookup formulas on our website . Because Last Milestone is both a formula column and a reference column, we\u2019ve also done the following which follows the formula described in step 2 of this tutorial. The Status field uses the formula $Last_Milestone.Round to look up the related milestone\u2019s round status. The Updated On field uses the formula $Last_Milestone.Date to look up the related milestone\u2019s date. By doing this, status and date also auto-update when the Last Milestone field updates. Still need help? Take a peek at the tutorial solution , or contact us at support@getgrist.com .","title":"Dig Deeper: Combining formulas and reference columns."},{"location":"examples/2021-06-timesheets/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Summary Tables Guide # Mastering Summary Tables with 2 Concepts # In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide. Using Summary Tables to Analyze Data # In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages. Creating Summary Tables # Step 1: Create a summary table # Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time. Step 2: Create summary tables with multiple categories # It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas. Calculating Totals Using Summary Formulas # Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages. Step 1: Understanding $group field in formulas # In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) . Step 2: Using $group in formulas # Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Summary tables guide"},{"location":"examples/2021-06-timesheets/#summary-tables-guide","text":"","title":"Summary Tables Guide"},{"location":"examples/2021-06-timesheets/#mastering-summary-tables-with-2-concepts","text":"In Grist, summary tables are the workhorse of data analysis. They are similar to pivot tables in spreadsheets, and group-by functions in databases. They make it possible to summarize tables by grouping records into specific categories. For example, if you have a table of Olympic champions, it may be useful to group champion records by country or by sport \u2014 or by a combination of country and sport. Once records are sorted into useful categories, you may then want to compute sums using those record groups. Grist makes use of a nifty special field, available in formulas as $group . Summary tables make it easy to build pages in which you can quickly capture useful data insights. You can learn to do this by learning two concepts: creating summary tables, and using $group in formulas. We\u2019ve created a simple template ( https://public.getgrist.com/x527ESJATWNS/Time-Sheets-Tutorial ) that you can copy and edit as you follow along with this guide.","title":"Mastering Summary Tables with 2 Concepts"},{"location":"examples/2021-06-timesheets/#using-summary-tables-to-analyze-data","text":"In our example, you work for the HR department of a candy factory that hires contractors that work for various departments. In the template, your team has created an advanced time sheet tracker. There is a page where contractors can easily add time sheets for select months. It would be useful to have additional pages that summarize expenses by month, by department, and by contractor. Your goal in this tutorial is to build those summary pages.","title":"Using Summary Tables to Analyze Data"},{"location":"examples/2021-06-timesheets/#creating-summary-tables","text":"","title":"Creating Summary Tables"},{"location":"examples/2021-06-timesheets/#step-1-create-a-summary-table","text":"Let\u2019s start by creating a summary table that groups records in the time sheet table by month. Grist makes it easy to create summary tables. Simply click on Add New > Add Page > Time Sheet Entries > > Months . Doing so will generate a summary table with some columns. In this case we have the following three columns. First Column: List of month groups, January through June of 2021. Second Column: A count of the total number of records in each month, e.g. in January there are 12 time sheet records. Third Column: A sum of hours worked in each month, e.g. in January the total hours worked in those 12 time sheet records is 81 hours. Note: Grist will automatically take any numerical columns and add them up, saving you some time.","title":"Step 1: Create a summary table"},{"location":"examples/2021-06-timesheets/#step-2-create-summary-tables-with-multiple-categories","text":"It may be useful to also group timesheets by a combination of categories. For example, we may want to know how much time and money was spent on a particular account in a particular month, or on a specific employee in an specific account and month. When creating a summary table, you can select multiple columns by which to group data. Let\u2019s add two new summary tables. When adding these widgets, be sure to Select By the first widget we created on this page. This will link the tables so that selecting a month in the first widget will dynamically update records in the new widgets. Not sure why? Brush up on linking widgets . Add New > Add Widget to Page > Time Sheet Entries > > Months and Account Add New > Add Widget to Page > Time Sheet Entries > > Months and Account and Employee To more easily view this data, drag and drop the new tables so that they tile vertically. You may also want to hide the count columns. Great! But now we want to add the total dollar spend in each of these categories. That\u2019s simple to do with summary formulas.","title":"Step 2: Create summary tables with multiple categories"},{"location":"examples/2021-06-timesheets/#calculating-totals-using-summary-formulas","text":"Following along with the video? Visit the tutorial solution if you get stuck. Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Calculating Totals Using Summary Formulas"},{"location":"examples/2021-06-timesheets/#step-1-understanding-group-field-in-formulas","text":"In Grist, $group is a special Python object that represents a collection of records that are summarized by the current summary line. If you look at the formula in the Hours Worked column, you will see SUM($group.Hours_Worked) . That is taking the sum of Hours Worked in a group . In this example, in row 1, the group is January 2021. Thus, in that row, the formula is adding hours worked in the January 2021 group. In row 2, the group is February 2021 and the formula sums hours worked in February. If you take a peek at the count column, which we hid previously, you\u2019ll find the formula len($group) . The function len (which stands for length) counts all the records that belong to the group being summarized in a particular row. Another way to express a set of records is by scanning through the list of records in a group using a variable. You can name the variable as you wish; we will use r (for \u201crecord\u201d). We can rewrite the formula in Hours Worked as sum(r.Hours_Worked for r in $group) .","title":"Step 1: Understanding $group field in formulas"},{"location":"examples/2021-06-timesheets/#step-2-using-group-in-formulas","text":"Let\u2019s calculate the total dollar spend on hours worked in each month. The formula is sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group) . Since each record r in the group is a record in the underlying table ( Time Sheet Entries ), r.Hours_Worked refers to the field in that table. In the Time Sheet Entries table, the column TimeSheet is a reference column that is referencing an entire record in the Time Sheets table. Thus, we have to further specify which field from the referenced record should be included in the formula\u2019s calculation, which in this case is Hourly_Rate . To learn more about reference columns, visit our Reference column guide . We can apply the same formula to the other two summary tables on this page. In the second table, the $group function is grouping things in the same month AND account. In the third table, the $group function is grouping expenses in the same month AND account AND by the same employee. And that\u2019s it! You did it. You rebuilt the month summary page. See if you can apply these concepts to rebuild the contractor summary page. Did you know? If needed, you can also add conditions to this formula. For example, sum(r.Hours_Worked * r.TimeSheet.Hourly_Rate for r in $group if r.Hours_Worked > 0) will only add up records in which there is a positive value in the field Hours Worked . Visit our function reference to learn more. Need more help? Visit the tutorial solution or contact us at support@getgrist.com . Note that there are access rules in place for the tutorial solution which will prevent you from seeing certain pages and most data. Make a copy to become the document owner and see all data and pages.","title":"Step 2: Using $group in formulas"},{"location":"examples/2021-07-auto-stamps/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Automatic Time and User Stamps Guide # It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template Template Overview: Grant Application Tracker # In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks. Creating Time Stamp Columns # Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps. Creating User Stamp Columns # User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job! Dig Deeper: Combining time and user stamps using formulas # Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Time and user stamps"},{"location":"examples/2021-07-auto-stamps/#automatic-time-and-user-stamps-guide","text":"It is sometimes useful to know when a record was last updated or created, and by whom. Grist makes it simple to create columns that stamp the time or a user\u2019s name to a record when it is updated or created. This makes it possible to sort records by age, determine how long a record has existed, or quickly track the most recent edit to its author. This is especially useful when working in a team. Suppose you have a document tracking sales opportunities. It may be useful to know the last time a sales person took action on a particular opportunity, and further determine for how long that opportunity has been pending. We\u2019ve created a tutorial based on our Grant Application Tracker template. To follow along with this guide, start with this incomplete version of the template that is missing time and user stamp columns. We will recreate those together in this tutorial. Create a copy and edit the template as you follow along with this guide. Open Tutorial Template","title":"Automatic Time and User Stamps Guide"},{"location":"examples/2021-07-auto-stamps/#template-overview-grant-application-tracker","text":"In this template, an NGO focused on ocean conservation projects tracks their grant applications submitted to marine-focused foundations. Here\u2019s a breakdown of the document structure. The Applications Dashboard shows all applications, application details, foundation details, and related tasks in one helpful view. Tasks by Staff lists the NGO\u2019s personnel, the applications they\u2019re overseeing, and tasks assigned to each team member. Our Programs lists the programs the NGO is seeking to fund. Our Funding Overview provides an overview of funding with two charts: (i) breakdown of funds in the fundraising pipeline, and (ii) breakdown of total funding awarded to specific NGO programs. Foundations We Work With lists the foundations to which the NGO has applied for funding. This page also lists related applications and tasks.","title":"Template Overview: Grant Application Tracker"},{"location":"examples/2021-07-auto-stamps/#creating-time-stamp-columns","text":"Columns can stamp the time when a record was created or updated by using the NOW() formula. Let\u2019s add a column to the Tasks table to track when a task was last updated. This can be accomplished in three steps. In the Tasks table, create a column labeled \u2018Last Updated\u2019 and in column types, select DateTime to select your desired format for date and time . Convert the column to a data column by clicking the ACTIONS dropdown in the creator panel. This prevents the formula from triggering whenever the document loads. Once converted to a data column, enter the NOW() formula. You will see two checkbox options below the formula. Apply to new records triggers the formula only when a record is created. Apply on record changes triggers the formula when a record is updated. Select Apply on record changes to open a submenu where you may select which fields, when updated, will trigger the formula. In this case, we\u2019ll select Any Field which means that updating any field in this record will trigger the time stamp formula to update. Great! You are now tracking when a task was last updated. Next, you may want to know who created a task because that person may have the most information about the task\u2019s goals and parameters. That\u2019s just as simple to create in Grist as time stamps.","title":"Creating Time Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#creating-user-stamp-columns","text":"User stamps are created using the exact same steps as time stamps, expect for one detail \u2014 the formula is user.Name . Let\u2019s add a column to the Tasks table to track who created a task. In the Tasks table, create a column labeled \u2018Created By\u2019 and in column types, select Text and modify the format if you wish. Convert formula column to a data column and enter the user.Name formula. Select Apply to new records so that the formula only triggers when a new record is created. There are other possibilities available in addition to user.Name such as user.Email or a unique user.UserID . The user information available is the same as that in access rule conditions . Note. It is still possible for a user to manually edit cells with time stamp or user stamp formulas. If you don\u2019t want that to be allowed, use access rules to forbid it. And that\u2019s it! You\u2019ve created columns that capture user and time stamp information based on specific triggers, such as when a record is updated or created. Great job!","title":"Creating User Stamp Columns"},{"location":"examples/2021-07-auto-stamps/#dig-deeper-combining-time-and-user-stamps-using-formulas","text":"Typically it is best practice to keep time and user stamp information in separate columns so that you may later sort and filter by those columns. However, there may be times when it would be useful to capture both time and user stamp information in the same column. Let\u2019s add such a column to the Applications table. In the Applications table, create a column labeled \u2018Created At\u2019 and convert to a data column. In column types, select Text. Enter the formula \"{:%b %d, %Y} by {}\".format(NOW(), user.Name) . The part in quotes is a format string. Each set of curly brackets {} in it gets replaced with the next value in parentheses after .format . Note that the format of the date is set within the corresponding {} . The format is set with a colon : followed by the specific Python codes %b , %d , %Y . Here is a table summarizing all the date format options available in Python . Select Apply to new records so that the formula only triggers when a new record is created. Need more help? Visit the final template or contact us at support@getgrist.com .","title":"Dig Deeper: Combining time and user stamps using formulas"},{"location":"examples/2023-01-acl-memo/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Access Rules to Restrict Duplicate Records # Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Restrict duplicate records"},{"location":"examples/2023-01-acl-memo/#access-rules-to-restrict-duplicate-records","text":"Suppose we have a table listing airports, and we want to forbid entry of a new record with the same airport code as an existing one. In the table, we can add a column named Count that counts how many records have the same code as each other: To understand this formula, visit our formula cheat sheet example for finding duplicates. Now, we can add an access rule to forbid any record update or creation that would result in a Count above one. We can also include a memo to explain the problem: newRec This variable is available for record/row creation and updating, and contains the state of a row after a proposed change, allowing you to selectively allow or deny certain changes. See checking new values for details. Now if we try to add a new row with an existing code, we get a helpful error: See Access rule conditions for details on writing access rule conditions, and Formulas to learn more about using formulas in columns.","title":"Access Rules to Restrict Duplicate Records"},{"location":"examples/2023-07-proposals-contracts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Creating Proposals # If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there. Setting up a Project table # First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables. Creating templates # Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data()) Setting up a proposal dashboard # Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data. Entering customer information # Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} . Printing and Saving # Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown. Setting up multiple forms # You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Proposals & contracts"},{"location":"examples/2023-07-proposals-contracts/#creating-proposals","text":"If you are keeping business details and contracts in Grist, it can be convenient to generate proposals and contracts right there, alongside those records. You can use the Markdown Custom Widget to create a custom \u2018form\u2019 for Proposals, Contracts, or many other types of documents. This tutorial shows you how to set up a document like this: You can find a finished template here: \ud83d\udcdd Proposals & Contracts Template . If you\u2019d like to add a proposal to an existing document, understanding this tutorial should get you there.","title":"Creating Proposals"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-project-table","text":"First of all, make a table to record project details by creating an empty document and renaming Table1 to Projects : We\u2019ll create our Proposal template alongside our Projects table. We can insert column IDs as placeholders in our Proposal template that will then be replaced by the cell value for the selected project. For example, in the screenshot below, the value in the Project Name column will replace the variable {Project_Name} in the proposal template on the right. Seeing the available columns while creating our proposal will make it easier to populate those variables.","title":"Setting up a Project table"},{"location":"examples/2023-07-proposals-contracts/#creating-templates","text":"Let\u2019s add a new table, Templates , to the page to store our template data. Add two columns: Name and Template Formatting . Now, let\u2019s add a custom widget beside the table to view our Template Formatting . Click the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Templates . Under \u2018Select By\u2019, select Templates again. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. Since we will be editing the template directly in the custom widget, you must allow \u2018Full document access\u2019 under Access Level so the widget can update the Templates table. Under \u2018Content\u2019, select the column Template Formatting . This is the column that will be updated when we make edits within the custom widget. Create a Template in the Templates table by entering a value in the Name column. Then, start editing the template formatting within the custom widget. The widget uses Markdown formatting to format the text. For help on Markdown, click the ? at the top of the widget to view the Markdown Guide . When you click the \u2018save\u2019 icon, formatting from the widget will populate the Template Formatting column. We will exclusively use the custom widget to edit the template formatting so this column can be hidden from the table view. To hide the column, right-click on the column header then \u2018Hide column\u2019. In your template, you\u2019ll have details and text that remain the same across all projects such as formatting, section headers and your own company\u2019s information. That is the information you\u2019ll type directly into the template. You\u2019ll also have information that changes, such as Project Name or Customer Name . We can use variables containing column IDs as placeholders for that dynamic data. Project Name , Customer Name and Customer Address will all change based on the selected Project. So, this is information we should store in our Projects table. Add the columns Project Name , Customer Name and Customer Address to the Projects table. We can use the column IDs for each of these columns as placeholders in our template with the format {COLUMN_ID} . A column\u2019s ID can be found under the \u2018Table\u2019 tab of the Creator Panel, directly under the Column Label. Finish building out your template to fit your needs. Be sure to add a column to your Projects table for all variable information. Finally, we need to add a formula column that will create our unique proposals. This formula column will combine the template formatting we just created with our project-specifc data. Add a new column to the Projects table with the following formula: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data())","title":"Creating templates"},{"location":"examples/2023-07-proposals-contracts/#setting-up-a-proposal-dashboard","text":"Next, we\u2019ll want to populate our proposal template with actual project data! Start creating a Proposal Dashboard by adding a new page to your document. Click the green \u2018Add New\u2019 button then \u2018Add Page\u2019. Under \u2018Select Widget\u2019, select \u2018Table\u2019 and under \u2018Select Data\u2019, select Projects . You\u2019ll notice that having this information in a table view is a bit busy. A Card widget will help simplify our view. Add a new widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Card\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Your dashboard should look similar to the screenshot below. Now that we have all of our Project details in a Card view, we can hide them from our table view. Under the \u2018Table\u2019 tab of the Creator Panel, select all columns except Project Name and Customer Name then click the green \u2018Hide Columns\u2019 button. Keeping most project details in the Card widget, rather than the Table widget, simplifies our dashboard. You can easily see all projects in the Table widget, and when you want to see details for a specific project, select the project and the Card widget will update to show you project details. Finally, we\u2019ll want to add a view of our project-specific proposal. Add a new custom widget to the page by clicking the green \u2018Add New\u2019 button then \u2018Add widget to page\u2019. Under \u2018Select Widget\u2019, select \u2018Custom\u2019 and under \u2018Select Data\u2019, select Projects . Under \u2018Select By\u2019, select Projects and add to page. Configure the custom widget by selecting \u2018Markdown\u2019 from the \u2018Custom\u2019 dropdown. You must allow \u2018Full document access\u2019. Under \u2018Content\u2019, select the column Proposal . This is the formula column that combines our template formatting with our project-specific data. Customize your layout by rearranging and resizing widgets. Add project details for a new project and see how your proposal updates to display the newly added data.","title":"Setting up a proposal dashboard"},{"location":"examples/2023-07-proposals-contracts/#entering-customer-information","text":"Now, let\u2019s make two useful changes to the Project set-up: Put customer information in a separate table, so we don\u2019t have to re-enter their address every time we create a proposal for them (and we can import the addresses in bulk). Update the formula in the Proposal column of the Projects table to look for information in another table. First, create a new table called Customers for customer-specific information like Name and Address . Some of this data is included in our Projects data set. To avoid duplicating data, we need to update our Customer Name and Customer Address columns to pull from our Customers table. On our Proposals Dashboard page, select the Customer Name field then update the column type to Reference . Confirm that \u2018Data from Table\u2019 is set to Customers and \u2018Show Column\u2019 is Name . Next, we need to update the Customer Address field to pull the address for the customer listed in the Customer Name column. Update the Customer Address column to use the following formula: $Customer_Name.Address This formula uses our reference column, Customer , along with dot notation , to pull the value from the Address column of the referenced table. When you take a look at a proposal for an existing project, you\u2019ll notice that the Customer Name no longer populates. This is because of the way reference columns store data. Although under \u2018Show Column\u2019, we chose to see the value from the Name column of the referenced table, reference columns actually store a record\u2019s ID. That is what we are seeing now in the proposal. We can modify our formula in the Proposal column to look for data in other tables. In the Projects table, update the formula in the Proposals column to the following: # Finds all data associated with this record class Find_Data(dict): def __missing__(self, key): return getattr(rec, key) # Finds the \"Proposal\" template in the Templates table template = Templates.lookupOne(Name=\"Proposal\").Template_Formatting # Formats the template with fields from this table as well as fields from the referenced table template.format_map(Find_Data( Customer_Name = $Customer_Name.Name, )) In the last portion of the formula, we can specify variables that pull from other tables. Customer_Name = $Customer_Name.Name is for our reference column, Customer Name . It uses dot notation to specify what data to pull from the referenced table. Note: Customer Address The Customer Address column can be deleted from the Projects table completely. This data is already stored in the Customers table and our Customer Name column is a reference column pointing to this table. We can use this reference column to pull any other information from the Customers table to include in our proposal. If you choose to delete Customer Address from the Projects table, update the last section of formula to the following: template.format_map(Find_data( Customer_Name = $Customer_Name.Name, Customer_Address = $Customer_Name.Address.replace('\\n', '
    '), )) This tells the formula what to use in place of the variable {Customer_Address} .","title":"Entering customer information"},{"location":"examples/2023-07-proposals-contracts/#printing-and-saving","text":"Once your proposal is ready to go, you can print it or save it as a PDF. Click the three-dot icon at the upper-right of the widget then select \u2018Print widget\u2019. From here, you can either select a printer or choose \u2018Save as PDF\u2019 from the \u2018Destination\u2019 dropdown.","title":"Printing and Saving"},{"location":"examples/2023-07-proposals-contracts/#setting-up-multiple-forms","text":"You can add more form templates by following the same steps that we have just completed. Add a new template to the Templates table then build out the template using variables containing column IDs for any data that is project-specific. If you have some sections that are the same as another form, copy it over to save yourself the trouble of retyping! Create a dashboard where you can select a project and enter details for this form then preview the form in a custom widget. Don\u2019t forget, you\u2019ll need to add a formula column that combines the new form template with details for the selected project! This formula column is what you\u2019ll select under the \u2018Content\u2019 dropdown while configuring the Markdown Custom Widget .","title":"Setting up multiple forms"},{"location":"keyboard-shortcuts/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Grist Shortcuts # General # Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence Navigation # Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget Selection # Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link Editing # Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time Data manipulation # Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Keyboard shortcuts"},{"location":"keyboard-shortcuts/#grist-shortcuts","text":"","title":"Grist Shortcuts"},{"location":"keyboard-shortcuts/#general","text":"Key (Mac) Key (Windows) Description F1 , \u2318 / F1 , Ctrl + / Display shortcuts pane \u2318 Z Ctrl + Z Undo last action \u2318 \u21e7 Z , \u2303 Y Ctrl + Shift + Z , Ctrl + Y Redo last action \u2318 F Ctrl + F Find \u2318 G Ctrl + G Find next occurrence \u2318 \u21e7 G Ctrl + Shift + G Find previous occurrence","title":"General"},{"location":"keyboard-shortcuts/#navigation","text":"Key (Mac) Key (Windows) Description \u2193 \u2193 Move downward to next record or field \u2191 \u2191 Move upward to previous record or field \u2192 \u2192 Move right to the next field \u2190 \u2190 Move left to the previous field Tab Tab Move to the next field, saving changes if editing a value \u21e7 Tab Shift + Tab Move to the previous field, saving changes if editing a value PageDown PageDown Move down one page of records, or to next record in a card list PageUp PageUp Move up one page of records, or to previous record in a card list \u2318 \u2191 Ctrl + \u2191 Move up to the first record \u2318 \u2193 Ctrl + \u2193 Move down to the last record Home Home Move to the first field or the beginning of a row End End Move to the last field or the end of a row \u2325 \u2193 Alt + \u2193 Open next page \u2325 \u2191 Alt + \u2191 Open previous page \u2318 O Ctrl + O Activate next page widget \u2318 \u21e7 O Ctrl + Shift + O Activate previous page widget Space Space Opens a record card in a table widget","title":"Navigation"},{"location":"keyboard-shortcuts/#selection","text":"Key (Mac) Key (Windows) Description \u21e7 \u2193 Shift + \u2193 Adds the element below the cursor to the selected range \u21e7 \u2191 Shift + \u2191 Adds the element above the cursor to the selected range \u21e7 \u2192 Shift + \u2192 Adds the element to the right of the cursor to the selected range \u21e7 \u2190 Shift + \u2190 Adds the element to the left of the cursor to the selected range \u2318 A Ctrl + A Selects all currently displayed cells \u2318 Shift \u2191 Ctrl + Shift + \u2191 Selects cells above the selected cell in the same column \u2318 Shift \u2193 Ctrl + Shift + \u2193 Selects cells below the selected cell in the same column \u2318 Shift \u2192 Ctrl + Shift + \u2192 Selects cells to the right of the selected cell in the same row \u2318 Shift \u2190 Ctrl + Shift + \u2190 Selects cells to the left of the selected cell in the same row \u2318 \u21e7 A Ctrl + Shift + A Copy anchor link","title":"Selection"},{"location":"keyboard-shortcuts/#editing","text":"Key (Mac) Key (Windows) Description Enter , F2 Enter , F2 Start editing the currently-selected cell Enter Enter Finish editing a cell, saving the value Escape Escape Discard changes to a cell value \u2318 D Ctrl + D Fills current selection with the contents of the top row in the selection Delete Backspace , Delete Clears the currently selected cells Enter Enter Toggles the value of checkbox cells = = When typed at the start of a cell, make this a formula column \u2318 ; Ctrl + ; Insert the current date \u2318 \u21e7 ; Ctrl + Shift + ; Insert the current date and time","title":"Editing"},{"location":"keyboard-shortcuts/#data-manipulation","text":"Key (Mac) Key (Windows) Description \u2318 \u21e7 Enter Ctrl + Shift + Enter Insert a new record, before the currently selected one in an unsorted table \u2318 Enter Ctrl + Enter Insert a new record, after the currently selected one in an unsorted table \u2318 \u21e7 D Ctrl + Shift + D Duplicate the currently selected record(s) \u2318 Delete Ctrl + Backspace , Ctrl + Delete Delete the currently selected record(s) \u2325 \u21e7 = Alt + Shift + = Insert a new column, before the currently selected one \u2325 = Alt + = Insert a new column, after the currently selected one \u2303 M Ctrl + M Rename the currently selected column \u2325 \u21e7 - Alt + Shift + - Hide the currently selected column \u2325 - Alt + - Delete the currently selected columns","title":"Data manipulation"},{"location":"functions/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . /* Makes headers for functions in-line with the \"expand\" arrow */ .wm-page-content summary h4 { display: inline-block; font-size: 14px; } Function Reference # Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE Grist # class Record # A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name) $ Field or rec .Field # Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name . $group # In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products class RecordSet # A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) . RecordSet. find.* (value) # A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role. class UserTable # Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas. UserTable. all # The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all) UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . Cumulative # NEXT (rec, *, group_by=(), order_by) # Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details. PREVIOUS (rec, *, group_by=(), order_by) # Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\")) RANK (rec, *, group_by=(), order_by, order=\u2019asc\u2019) # Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score. Date # DATE (year, month, day) # Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16) DATEADD (start_date, days=0, months=0, years=0, weeks=0) # Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26) DATEDIF (start_date, end_date, unit) # Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14 DATEVALUE (date_string, tz=None) # Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York')) DATE_TO_XL (date_value) # Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625 DAY (date) # Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1 DAYS (end_date, start_date) # Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42 DTIME (value, tz=None) # Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) EDATE (start_date, months) # Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1) EOMONTH (start_date, months) # Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31) HOUR (time) # Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0 ISOWEEKNUM (date) # Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1] MINUTE (time) # Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58 MONTH (date) # Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1 MOONPHASE (date, output=\u2019emoji\u2019) # Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9] NOW (tz=None) # Returns the datetime object for the current time. SECOND (time) # Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59 TODAY (tz=None) # Returns the date object for the current date. WEEKDAY (date, return_type=1) # Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3 WEEKNUM (date, return_type=1) # Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5 XL_TO_DATE (value, tz=None) # Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York')) YEAR (date) # Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900 YEARFRAC (start_date, end_date, basis=0) # Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219' Info # CELL (info_type, reference) # Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist. ISBLANK (value) # Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist. ISEMAIL (value) # Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False ISERR (value) # Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False ISERROR (value) # Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True ISLOGICAL (value) # Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False ISNA (value) # Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False ISNONTEXT (value) # Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True ISNUMBER (value) # Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False ISREF (value) # Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False ISREFLIST (value) # Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False ISTEXT (value) # Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False ISURL (value) # Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False N (value) # Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0 NA () # Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True PEEK (func) # Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems. RECORD (record_or_list, dates_as_iso=False, expand_refs=0) # Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\")) REQUEST (url, params=None, headers=None, method=\u2019GET\u2019, data=None, json=None) # Note This function is not currently implemented in Grist. TYPE (value) # Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist. Logical # AND (logical_expression, *logical_expressions) # Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False FALSE () # Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False IF (logical_expression, value_if_true, value_if_false) # Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0 IFERROR (value, value_if_error=\u2019\u2018) # Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) '' NOT (logical_expression) # True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True OR (logical_expression, *logical_expressions) # Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True TRUE () # Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True Lookup # UserTable. lookupOne (Field_In_Lookup_Table=value, \u2026) # Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date. UserTable. lookupRecords (Field_In_Lookup_Table=value, \u2026) # Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords . ADDRESS (row, column, absolute_relative_mode, use_a1_notation, sheet) # Returns a cell reference as a string. Note This function is not currently implemented in Grist. CHOOSE (index, choice1, choice2) # Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist. COLUMN (cell_reference=None) # Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist. COLUMNS (range) # Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist. CONTAINS (value, match_empty=no_match_empty) # Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual. GETPIVOTDATA (value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args) # Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist. HLOOKUP (search_key, range, index, is_sorted) # Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist. HYPERLINK (url, link_label) # Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist. INDEX (reference, row, column) # Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist. INDIRECT (cell_reference_as_string) # Returns a cell reference specified by a string. Note This function is not currently implemented in Grist. LOOKUP (search_key, search_range_or_search_result_array, result_range=None) # Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist. MATCH (search_key, range, search_type) # Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist. OFFSET (cell_reference, offset_rows, offset_columns, height, width) # Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist. ROW (cell_reference) # Returns the row number of a specified cell. Note This function is not currently implemented in Grist. ROWS (range) # Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist. SELF_HYPERLINK (label=None, page=None, **kwargs) # Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME) VLOOKUP (table, **field_value_pairs) # Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age Math # ABS (value) # Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4 ACOS (value) # Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0 ACOSH (value) # Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228 ARABIC (roman_numeral) # Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912 ASIN (value) # Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0 ASINH (value) # Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295 ATAN (value) # Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0 ATAN2 (x, y) # Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718 ATANH (value) # Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348 CEILING (value, factor=1) # Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24 COMBIN (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120 COS (angle) # Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5 COSH (value) # Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251 DEGREES (angle) # Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0 EVEN (value) # Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2 EXP (exponent) # Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561 FACT (value) # Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values FACTDOUBLE (value) # Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8 FLOOR (value, factor=1) # Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23 GCD (value1, *more_values) # Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7 INT (value) # Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5 LCM (value1, *more_values) # Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72 LN (value) # Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0 LOG (value, base=10) # Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473 LOG10 (value) # Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0 MOD (dividend, divisor) # Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1 MROUND (value, factor) # Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid MULTINOMIAL (value1, *more_values) # Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860 NUM (value) # For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA' ODD (value) # Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3 PI () # Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388 POWER (base, exponent) # Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249 PRODUCT (factor1, *more_factors) # Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500 QUOTIENT (dividend, divisor) # Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3 RADIANS (angle) # Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389 RAND () # Returns a random number between 0 inclusive and 1 exclusive. RANDBETWEEN (low, high) # Returns a uniformly random integer between two values, inclusive. ROMAN (number, form_unused=None) # Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII' ROUND (value, places=0) # Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0 ROUNDDOWN (value, places=0) # Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400 ROUNDUP (value, places=0) # Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500 SERIESSUM (x, n, m, a) # Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103 SIGN (value) # Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1 SIN (angle) # Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5 SINH (value) # Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491 SQRT (value) # Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0 SQRTPI (value) # Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628 SUBTOTAL (function_code, range1, range2) # Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist. SUM (value1, *more_values) # Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52 SUMIF (records, criterion, sum_range) # Returns a conditional sum across a range. Note This function is not currently implemented in Grist. SUMIFS (sum_range, criteria_range1, criterion1, *args) # Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist. SUMPRODUCT (array1, *more_arrays) # Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0 SUMSQ (value1, value2) # Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist. TAN (angle) # Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0 TANH (value) # Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117 TRUNC (value, places=0) # Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0 UUID () # Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time. Schedule # SCHEDULE (schedule, start=None, count=10, end=None) # Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00'] Stats # AVEDEV (value1, value2) # Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist. AVERAGE (value, *more_values) # Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero AVERAGEA (value, *more_values) # Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5 AVERAGEIF (criteria_range, criterion, average_range=None) # Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist. AVERAGEIFS (average_range, criteria_range1, criterion1, *args) # Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist. AVERAGE_WEIGHTED (pairs) # Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7 BINOMDIST (num_successes, num_trials, prob_success, cumulative) # Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist. CONFIDENCE (alpha, standard_deviation, pop_size) # Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist. CORREL (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. COUNT (value, *more_values) # Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0 COUNTA (value, *more_values) # Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2 COVAR (data_y, data_x) # Calculates the covariance of a dataset. Note This function is not currently implemented in Grist. CRITBINOM (num_trials, prob_success, target_prob) # Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist. DEVSQ (value1, value2) # Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist. EXPONDIST (x, lambda_, cumulative) # Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist. FDIST (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. FISHER (value) # Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FISHERINV (value) # Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist. FORECAST (x, data_y, data_x) # Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist. F_DIST (x, degrees_freedom1, degrees_freedom2, cumulative) # Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. F_DIST_RT (x, degrees_freedom1, degrees_freedom2) # Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist. GEOMEAN (value1, value2) # Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist. HARMEAN (value1, value2) # Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist. HYPGEOMDIST (num_successes, num_draws, successes_in_pop, pop_size) # Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist. INTERCEPT (data_y, data_x) # Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist. KURT (value1, value2) # Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist. LARGE (data, n) # Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. LOGINV (x, mean, standard_deviation) # Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. LOGNORMDIST (x, mean, standard_deviation) # Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist. MAX (value, *more_values) # Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2) MAXA (value, *more_values) # Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MEDIAN (value, *more_values) # Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number MIN (value, *more_values) # Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) MINA (value, *more_values) # Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0 MODE (value1, value2) # Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist. NEGBINOMDIST (num_failures, num_successes, prob_success) # Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist. NORMDIST (x, mean, standard_deviation, cumulative) # Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMINV (x, mean, standard_deviation) # Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist. NORMSDIST (x) # Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist. NORMSINV (x) # Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist. PEARSON (data_y, data_x) # Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. PERCENTILE (data, percentile) # Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist. PERCENTRANK (data, value, significant_digits=None) # Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_EXC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERCENTRANK_INC (data, value, significant_digits=None) # Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist. PERMUT (n, k) # Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist. POISSON (x, mean, cumulative) # Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist. PROB (data, probabilities, low_limit, high_limit=None) # Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist. QUARTILE (data, quartile_number) # Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist. RANK_AVG (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist. RANK_EQ (value, data, is_ascending=None) # Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist. RSQ (data_y, data_x) # Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist. SKEW (value1, value2) # Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist. SLOPE (data_y, data_x) # Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist. SMALL (data, n) # Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist. STANDARDIZE (value, mean, standard_deviation) # Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist. STDEV (value, *more_values) # Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVA (value, *more_values) # Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero STDEVP (value, *more_values) # Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0 STDEVPA (value, *more_values) # Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0 STEYX (data_y, data_x) # Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist. TDIST (x, degrees_freedom, tails) # Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist. TINV (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. TRIMMEAN (data, exclude_proportion) # Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist. TTEST (range1, range2, tails, type) # Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist. T_INV (probability, degrees_freedom) # Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist. T_INV_2T (probability, degrees_freedom) # Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist. VAR (value1, value2) # Calculates the variance based on a sample. Note This function is not currently implemented in Grist. VARA (value1, value2) # Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist. VARP (value1, value2) # Calculates the variance based on an entire population. Note This function is not currently implemented in Grist. VARPA (value1, value2) # Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist. WEIBULL (x, shape, scale, cumulative) # Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist. ZTEST (data, value, standard_deviation) # Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist. Text # CHAR (table_number) # Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!' CLEAN (text) # Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report' CODE (string) # Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33 CONCAT (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' CONCATENATE (string, *more_strings) # Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' DOLLAR (number, decimals=2) # Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10' EXACT (string1, string2) # Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False FIND (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found FIXED (number, decimals=2, no_commas=False) # Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500' LEFT (string, num_chars=1) # Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid LEN (text) # Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11 LOWER (text) # Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b' MID (text, start_num, num_chars) # Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid PHONE_FORMAT (value, country=None, format=None) # Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first. PROPER (text) # Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget' REGEXEXTRACT (text, regular_expression) # Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match REGEXMATCH (text, regular_expression) # Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False REGEXREPLACE (text, regular_expression, replacement) # Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo' REPLACE (text, position, length, new_text) # Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid REPT (text, number_times) # Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid RIGHT (string, num_chars=1) # Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid SEARCH (find_text, within_text, start_num=1) # Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4 SUBSTITUTE (text, old_text, new_text, instance_num=None) # Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012' T (value) # Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u'' TASTEME (food) # For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False TEXT (number, format_type) # Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist. TRIM (text) # Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") '' UPPER (text) # Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B' VALUE (text) # Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"Function reference"},{"location":"functions/#function-reference","text":"Grist formulas support most Excel functions, as well as the Python programming language. The table below lists Grist-specific functions, and the suite of the included Excel-like functions. In addition, the entire Python standard library is available. For more about using formulas in Grist, see Intro to Formulas . Grist uses Python (version 3.11) for formulas. You can use nearly all features of Python (see Python documentation ). Here are some helpful notes: Python is case-sensitive, including for Grist table and column names. Excel-like functions are always in uppercase. E.g. if is a Python keyword, while IF is an Excel-like function. Compare for equality using == , in place of Excel\u2019s single = (which in Python means assignment). \u201cNot equal\u201d uses != in place of Excel\u2019s <> . You may write multi-line Python in formulas (use Shift + Enter to add lines), including statements, variables, imports, etc. Grist code runs in a secure sandbox, with no access to anything outside your document. Category Functions Grist Record or rec , $Field or rec.Field , $group or rec.group , RecordSet , find.* , UserTable , all , lookupOne , lookupRecords Cumulative NEXT , PREVIOUS , RANK Date DATE , DATEADD , DATEDIF , DATEVALUE , DATE_TO_XL , DAY , DAYS , DTIME , EDATE , EOMONTH , HOUR , ISOWEEKNUM , MINUTE , MONTH , MOONPHASE , NOW , SECOND , TODAY , WEEKDAY , WEEKNUM , XL_TO_DATE , YEAR , YEARFRAC Info CELL , ISBLANK , ISEMAIL , ISERR , ISERROR , ISLOGICAL , ISNA , ISNONTEXT , ISNUMBER , ISREF , ISREFLIST , ISTEXT , ISURL , N , NA , PEEK , RECORD , REQUEST , TYPE Logical AND , FALSE , IF , IFERROR , NOT , OR , TRUE Lookup lookupOne , lookupRecords , ADDRESS , CHOOSE , COLUMN , COLUMNS , CONTAINS , GETPIVOTDATA , HLOOKUP , HYPERLINK , INDEX , INDIRECT , LOOKUP , MATCH , OFFSET , ROW , ROWS , SELF_HYPERLINK , VLOOKUP Math ABS , ACOS , ACOSH , ARABIC , ASIN , ASINH , ATAN , ATAN2 , ATANH , CEILING , COMBIN , COS , COSH , DEGREES , EVEN , EXP , FACT , FACTDOUBLE , FLOOR , GCD , INT , LCM , LN , LOG , LOG10 , MOD , MROUND , MULTINOMIAL , NUM , ODD , PI , POWER , PRODUCT , QUOTIENT , RADIANS , RAND , RANDBETWEEN , ROMAN , ROUND , ROUNDDOWN , ROUNDUP , SERIESSUM , SIGN , SIN , SINH , SQRT , SQRTPI , SUBTOTAL , SUM , SUMIF , SUMIFS , SUMPRODUCT , SUMSQ , TAN , TANH , TRUNC , UUID Schedule SCHEDULE Stats AVEDEV , AVERAGE , AVERAGEA , AVERAGEIF , AVERAGEIFS , AVERAGE_WEIGHTED , BINOMDIST , CONFIDENCE , CORREL , COUNT , COUNTA , COVAR , CRITBINOM , DEVSQ , EXPONDIST , FDIST , FISHER , FISHERINV , FORECAST , F_DIST , F_DIST_RT , GEOMEAN , HARMEAN , HYPGEOMDIST , INTERCEPT , KURT , LARGE , LOGINV , LOGNORMDIST , MAX , MAXA , MEDIAN , MIN , MINA , MODE , NEGBINOMDIST , NORMDIST , NORMINV , NORMSDIST , NORMSINV , PEARSON , PERCENTILE , PERCENTRANK , PERCENTRANK_EXC , PERCENTRANK_INC , PERMUT , POISSON , PROB , QUARTILE , RANK_AVG , RANK_EQ , RSQ , SKEW , SLOPE , SMALL , STANDARDIZE , STDEV , STDEVA , STDEVP , STDEVPA , STEYX , TDIST , TINV , TRIMMEAN , TTEST , T_INV , T_INV_2T , VAR , VARA , VARP , VARPA , WEIBULL , ZTEST Text CHAR , CLEAN , CODE , CONCAT , CONCATENATE , DOLLAR , EXACT , FIND , FIXED , LEFT , LEN , LOWER , MID , PHONE_FORMAT , PROPER , REGEXEXTRACT , REGEXMATCH , REGEXREPLACE , REPLACE , REPT , RIGHT , SEARCH , SUBSTITUTE , T , TASTEME , TEXT , TRIM , UPPER , VALUE","title":""},{"location":"functions/#grist","text":"","title":"Grist"},{"location":"functions/#record","text":"A Record represents a record of data. It is the primary means of accessing values in formulas. A Record for a particular table has a property for each data and formula column in the table. In a formula, $field is translated to rec.field , where rec is the Record for which the formula is being evaluated. For example: def Full_Name(rec, table): return rec.First_Name + ' ' + rec.LastName def Name_Length(rec, table): return len(rec.Full_Name)","title":"Record"},{"location":"functions/#_field","text":"Access the field named \u201cField\u201d of the current record. E.g. $First_Name or rec.First_Name .","title":"$Field"},{"location":"functions/#_group","text":"In a summary table , $group is a special field containing the list of Records that are summarized by the current summary line. E.g. the formula len($group) counts the number of those records being summarized in each row. See RecordSet for useful properties offered by the returned object. Examples: sum($group.Amount) # Sum of the Amount field in the matching records sum(r.Amount for r in $group) # Same as sum($group.Amount) sum(r.Amount for r in $group if r > 0) # Sum of only the positive amounts sum(r.Shares * r.Price for r in $group) # Sum of shares * price products","title":"$group"},{"location":"functions/#recordset","text":"A RecordSet represents a collection of records, as returned by Table.lookupRecords() or $group property in summary views. A RecordSet allows iterating through the records: sum(r.Amount for r in Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\")) min(r.DueDate for r in Tasks.lookupRecords(Owner=\"Bob\")) RecordSets also provide a convenient way to access the list of values for a particular field for all the records, as record_set.Field . For example, the examples above are equivalent to: sum(Students.lookupRecords(First_Name=\"John\", Last_Name=\"Doe\").Amount) min(Tasks.lookupRecords(Owner=\"Bob\").DueDate) You can get the number of records in a RecordSet using len , e.g. len($group) .","title":"RecordSet"},{"location":"functions/#find_","text":"A set of methods for finding values in sorted sets of records, as returned by lookupRecords . For example: Transactions.lookupRecords(..., order_by=\"Date\").find.lt($Date) Table.lookupRecords(..., order_by=(\"Foo\", \"Bar\")).find.le(foo, bar) If the find attribute is shadowed by a same-named user column, you may use _find instead. The methods available are: lt : (less than) find nearest record with sort values < the given values le : (less than or equal to) find nearest record with sort values <= the given values gt : (greater than) find nearest record with sort values > the given values ge : (greater than or equal to) find nearest record with sort values >= the given values eq : (equal to) find nearest record with sort values == the given values Example from our Payroll template . Each person has a history of pay rates, in the Rates table. To find a rate applicable on a certain date, here is how you can do it old-style: # Get all the rates for the Person and Role in this row. rates = Rates.lookupRecords(Person=$Person, Role=$Role) # Pick out only those rates whose Rate_Start is on or before this row's Date. past_rates = [r for r in rates if r.Rate_Start <= $Date] # Select the latest of past_rates, i.e. maximum by Rate_Start. rate = max(past_rates, key=lambda r: r.Rate_Start) # Return the Hourly_Rate from the relevant Rates record. return rate.Hourly_Rate With the new methods, it is much simpler: rates = Rates.lookupRecords(Person=$Person, Role=$Role, order_by=\"Rate_Start\") rate = rates.find.le($Date) return rate.Hourly_Rate Note that this is also much faster when there are many rates for the same Person and Role.","title":"find.*"},{"location":"functions/#usertable","text":"Each data table in the document is represented in the code by an instance of UserTable class. These names are always capitalized. A UserTable provides access to all the records in the table, as well as methods to look up particular records. Every table in the document is available to all formulas.","title":"UserTable"},{"location":"functions/#all","text":"The list of all the records in this table. For example, this evaluates to the number of records in the table Students . len(Students.all) This evaluates to the sum of the Population field for every record in the table Countries . sum(r.Population for r in Countries.all)","title":"all"},{"location":"functions/#lookupone","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#cumulative","text":"","title":"Cumulative"},{"location":"functions/#next","text":"Finds the next record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details.","title":"NEXT"},{"location":"functions/#previous","text":"Finds the previous record in the table according to the order specified by order_by , and grouping specified by group_by . Each of these arguments may be a column ID or a tuple of column IDs, and order_by allows column IDs to be prefixed with \u201c-\u201d to reverse sort order. For example, PREVIOUS(rec, order_by=\"Date\") # The previous record when sorted by increasing Date. PREVIOUS(rec, order_by=\"-Date\") # The previous record when sorted by decreasing Date. You may use group_by to search for the previous record within a filtered group. For example, this finds the previous record with the same Account as rec , when records are filtered by the Account of rec and sorted by increasing Date: PREVIOUS(rec, group_by=\"Account\", order_by=\"Date\") When multiple records have the same order_by values (e.g. the same Date in the examples above), the order is determined by the relative position of rows in views. This is done internally by falling back to the special column manualSort and the row ID column id . Use order_by=None to find the previous record in an unsorted table (when rows may be rearranged by dragging them manually). For example: PREVIOUS(rec, order_by=None) # The previous record in the unsorted list of records. You may specify multiple column IDs as a tuple, for both group_by and order_by . This can be used to match views sorted by multiple columns. For example: PREVIOUS(rec, group_by=(\"Account\", \"Year\"), order_by=(\"Date\", \"-Amount\"))","title":"PREVIOUS"},{"location":"functions/#rank","text":"Returns the rank (or position) of this record in the table according to the order specified by order_by , and grouping specified by group_by . See PREVIOUS for details of these parameters. The order parameter may be \"asc\" (which is the default) or \"desc\" . When order is \"asc\" or omitted, the first record in the group in the sorted order would have the rank of 1. When order is \"desc\" , the last record in the sorted order would have the rank of 1. If there are multiple groups, there will be multiple records with the same rank. In particular, each group will have a record with rank 1. For example, RANK(rec, group_by=\"Year\", order_by=\"Score\", order=\"desc\") will return the rank of the current record ( rec ) among all the records in its table for the same year, ordered by decreasing score.","title":"RANK"},{"location":"functions/#date_1","text":"","title":"Date"},{"location":"functions/#date","text":"Returns the datetime.datetime object that represents a particular date. The DATE function is most useful in formulas where year, month, and day are formulas, not constants. If year is between 0 and 1899 (inclusive), adds 1900 to calculate the year. >>> DATE(108, 1, 2) datetime.date(2008, 1, 2) >>> DATE(2008, 1, 2) datetime.date(2008, 1, 2) If month is greater than 12, rolls into the following year. >>> DATE(2008, 14, 2) datetime.date(2009, 2, 2) If month is less than 1, subtracts that many months plus 1, from the first month in the year. >>> DATE(2008, -3, 2) datetime.date(2007, 9, 2) If day is greater than the number of days in the given month, rolls into the following months. >>> DATE(2008, 1, 35) datetime.date(2008, 2, 4) If day is less than 1, subtracts that many days plus 1, from the first day of the given month. >>> DATE(2008, 1, -15) datetime.date(2007, 12, 16)","title":"DATE"},{"location":"functions/#dateadd","text":"Returns the date a given number of days, months, years, or weeks away from start_date . You may specify arguments in any order if you specify argument names. Use negative values to subtract. For example, DATEADD(date, 1) is the same as DATEADD(date, days=1) , ands adds one day to date . DATEADD(date, years=1, days=-1) adds one year minus one day. >>> DATEADD(DATE(2011, 1, 15), 1) datetime.date(2011, 1, 16) >>> DATEADD(DATE(2011, 1, 15), months=1, days=-1) datetime.date(2011, 2, 14) >>> DATEADD(DATE(2011, 1, 15), years=-2, months=1, days=3, weeks=2) datetime.date(2009, 3, 4) >>> DATEADD(DATE(1975, 4, 30), years=50, weeks=-5) datetime.date(2025, 3, 26)","title":"DATEADD"},{"location":"functions/#datedif","text":"Calculates the number of days, months, or years between two dates. Unit indicates the type of information that you want returned: \u201cY\u201d: The number of complete years in the period. \u201cM\u201d: The number of complete months in the period. \u201cD\u201d: The number of days in the period. \u201cMD\u201d: The difference between the days in start_date and end_date. The months and years of the dates are ignored. \u201cYM\u201d: The difference between the months in start_date and end_date. The days and years of the dates are ignored. \u201cYD\u201d: The difference between the days of start_date and end_date. The years of the dates are ignored. Two complete years in the period (2) >>> DATEDIF(DATE(2001, 1, 1), DATE(2003, 1, 1), \"Y\") 2 440 days between June 1, 2001, and August 15, 2002 (440) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"D\") 440 75 days between June 1 and August 15, ignoring the years of the dates (75) >>> DATEDIF(DATE(2001, 6, 1), DATE(2012, 8, 15), \"YD\") 75 The difference between 1 and 15, ignoring the months and the years of the dates (14) >>> DATEDIF(DATE(2001, 6, 1), DATE(2002, 8, 15), \"MD\") 14","title":"DATEDIF"},{"location":"functions/#datevalue","text":"Converts a date that is stored as text to a datetime object. >>> DATEVALUE(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"30-Jan-2008\") datetime.datetime(2008, 1, 30, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"2008-12-11\") datetime.datetime(2008, 12, 11, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DATEVALUE(\"5-JUL\").replace(year=2000) datetime.datetime(2000, 7, 5, 0, 0, tzinfo=moment.tzinfo('America/New_York')) In case of ambiguity, prefer M/D/Y format. >>> DATEVALUE(\"1/2/3\") datetime.datetime(2003, 1, 2, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DATEVALUE"},{"location":"functions/#date_to_xl","text":"Converts a Python date or datetime object to the serial number as used by Excel, with December 30, 1899 as serial number 1. See XL_TO_DATE for more explanation. >>> DATE_TO_XL(datetime.date(2008, 1, 1)) 39448.0 >>> DATE_TO_XL(datetime.date(2012, 3, 14)) 40982.0 >>> DATE_TO_XL(datetime.datetime(2012, 3, 14, 1, 30)) 40982.0625","title":"DATE_TO_XL"},{"location":"functions/#day","text":"Returns the day of a date, as an integer ranging from 1 to 31. Same as date.day . >>> DAY(DATE(2011, 4, 15)) 15 >>> DAY(\"5/31/2012\") 31 >>> DAY(datetime.datetime(1900, 1, 1)) 1","title":"DAY"},{"location":"functions/#days","text":"Returns the number of days between two dates. Same as (end_date - start_date).days . >>> DAYS(\"3/15/11\",\"2/1/11\") 42 >>> DAYS(DATE(2011, 12, 31), DATE(2011, 1, 1)) 364 >>> DAYS(\"2/1/11\", \"3/15/11\") -42","title":"DAYS"},{"location":"functions/#dtime","text":"Returns the value converted to a python datetime object. The value may be a string , date (interpreted as midnight on that day), time (interpreted as a time-of-day today), or an existing datetime . The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. If the input is itself a datetime with the timezone set, it is returned unchanged (no changes to its timezone). >>> DTIME(datetime.date(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.date(2017, 1, 1), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('Europe/Paris')) >>> DTIME(datetime.datetime(2017, 1, 1)) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC'))) datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(datetime.datetime(2017, 1, 1, tzinfo=moment.tzinfo('UTC')), 'Europe/Paris') datetime.datetime(2017, 1, 1, 0, 0, tzinfo=moment.tzinfo('UTC')) >>> DTIME(\"1/1/2008\") datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York'))","title":"DTIME"},{"location":"functions/#edate","text":"Returns the date that is the given number of months before or after start_date . Use EDATE to calculate maturity dates or due dates that fall on the same day of the month as the date of issue. >>> EDATE(DATE(2011, 1, 15), 1) datetime.date(2011, 2, 15) >>> EDATE(DATE(2011, 1, 15), -1) datetime.date(2010, 12, 15) >>> EDATE(DATE(2011, 1, 15), 2) datetime.date(2011, 3, 15) >>> EDATE(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 1) >>> EDATE(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 1)","title":"EDATE"},{"location":"functions/#eomonth","text":"Returns the date for the last day of the month that is the indicated number of months before or after start_date. Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. >>> EOMONTH(DATE(2011, 1, 1), 1) datetime.date(2011, 2, 28) >>> EOMONTH(DATE(2011, 1, 15), -3) datetime.date(2010, 10, 31) >>> EOMONTH(DATE(2012, 3, 1), 10) datetime.date(2013, 1, 31) >>> EOMONTH(DATE(2012, 5, 1), -2) datetime.date(2012, 3, 31)","title":"EOMONTH"},{"location":"functions/#hour","text":"Same as time.hour . >>> HOUR(XL_TO_DATE(0.75)) 18 >>> HOUR(\"7/18/2011 7:45\") 7 >>> HOUR(\"4/21/2012\") 0","title":"HOUR"},{"location":"functions/#isoweeknum","text":"Returns the ISO week number of the year for a given date. >>> ISOWEEKNUM(\"3/9/2012\") 10 >>> [ISOWEEKNUM(DATE(2000 + y, 1, 1)) for y in [0,1,2,3,4,5,6,7,8]] [52, 1, 1, 1, 1, 53, 52, 1, 1]","title":"ISOWEEKNUM"},{"location":"functions/#minute","text":"Returns the minutes of datetime , as an integer from 0 to 59. Same as time.minute . >>> MINUTE(XL_TO_DATE(0.75)) 0 >>> MINUTE(\"7/18/2011 7:45\") 45 >>> MINUTE(\"12:59:00 PM\") 59 >>> MINUTE(datetime.time(12, 58, 59)) 58","title":"MINUTE"},{"location":"functions/#month","text":"Returns the month of a date represented, as an integer from from 1 (January) to 12 (December). Same as date.month . >>> MONTH(DATE(2011, 4, 15)) 4 >>> MONTH(\"5/31/2012\") 5 >>> MONTH(datetime.datetime(1900, 1, 1)) 1","title":"MONTH"},{"location":"functions/#moonphase","text":"Returns the phase of the moon on the given date. The output defaults to a moon-phase emoji. With output=\"days\" , the output is the age of the moon in days (new moon being 0). With output=\"fraction\" , the output is the fraction of the lunar month since new moon. The calculation isn\u2019t astronomically precise, but good enough for wolves and sailors. Do NOT! use output=\"lunacy\" . >>> MOONPHASE(datetime.date(1900, 1, 1), \"days\") 0.0 >>> MOONPHASE(datetime.date(1900, 1, 1), \"fraction\") 0.0 >>> MOONPHASE(datetime.datetime(1900, 1, 1)) == '\ud83c\udf11' True >>> MOONPHASE(datetime.date(1900, 1, 15)) == '\ud83c\udf15' True >>> MOONPHASE(datetime.date(1900, 1, 30)) == '\ud83c\udf11' True >>> [MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n)) for n in range(8)] == ['\ud83c\udf14', '\ud83c\udf15', '\ud83c\udf16', '\ud83c\udf17', '\ud83c\udf18', '\ud83c\udf11', '\ud83c\udf12', '\ud83c\udf13'] True >>> [round(MOONPHASE(DATEADD(datetime.date(2023, 4, 1), days=4*n), \"days\"), 1) for n in range(8)] [10.4, 14.4, 18.4, 22.4, 26.4, 0.9, 4.9, 8.9]","title":"MOONPHASE"},{"location":"functions/#now","text":"Returns the datetime object for the current time.","title":"NOW"},{"location":"functions/#second","text":"Returns the seconds of datetime , as an integer from 0 to 59. Same as time.second . >>> SECOND(XL_TO_DATE(0.75)) 0 >>> SECOND(\"7/18/2011 7:45:13\") 13 >>> SECOND(datetime.time(12, 58, 59)) 59","title":"SECOND"},{"location":"functions/#today","text":"Returns the date object for the current date.","title":"TODAY"},{"location":"functions/#weekday","text":"Returns the day of the week corresponding to a date. The day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by default. Return_type determines the type of the returned value. 1 (default) - Returns 1 (Sunday) through 7 (Saturday). 2 - Returns 1 (Monday) through 7 (Sunday). 3 - Returns 0 (Monday) through 6 (Sunday). 11 - Returns 1 (Monday) through 7 (Sunday). 12 - Returns 1 (Tuesday) through 7 (Monday). 13 - Returns 1 (Wednesday) through 7 (Tuesday). 14 - Returns 1 (Thursday) through 7 (Wednesday). 15 - Returns 1 (Friday) through 7 (Thursday). 16 - Returns 1 (Saturday) through 7 (Friday). 17 - Returns 1 (Sunday) through 7 (Saturday). >>> WEEKDAY(DATE(2008, 2, 14)) 5 >>> WEEKDAY(DATE(2012, 3, 1)) 5 >>> WEEKDAY(DATE(2012, 3, 1), 1) 5 >>> WEEKDAY(DATE(2012, 3, 1), 2) 4 >>> WEEKDAY(\"3/1/2012\", 3) 3","title":"WEEKDAY"},{"location":"functions/#weeknum","text":"Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Return_type determines which week is considered the first week of the year. 1 (default) - Week 1 is the first week starting Sunday that contains January 1. 2 - Week 1 is the first week starting Monday that contains January 1. 11 - Week 1 is the first week starting Monday that contains January 1. 12 - Week 1 is the first week starting Tuesday that contains January 1. 13 - Week 1 is the first week starting Wednesday that contains January 1. 14 - Week 1 is the first week starting Thursday that contains January 1. 15 - Week 1 is the first week starting Friday that contains January 1. 16 - Week 1 is the first week starting Saturday that contains January 1. 17 - Week 1 is the first week starting Sunday that contains January 1. 21 - ISO 8601 Approach: Week 1 is the first week starting Monday that contains January 4. Equivalently, it is the week that contains the first Thursday of the year. >>> WEEKNUM(DATE(2012, 3, 9)) 10 >>> WEEKNUM(DATE(2012, 3, 9), 2) 11 >>> WEEKNUM('1/1/1900') 1 >>> WEEKNUM('2/1/1900') 5","title":"WEEKNUM"},{"location":"functions/#xl_to_date","text":"Converts a provided Excel serial number representing a date into a datetime object. Value is interpreted as the number of days since December 30, 1899. (This corresponds to Google Sheets interpretation. Excel starts with Dec. 31, 1899 but wrongly considers 1900 to be a leap year. Excel for Mac should be configured to use 1900 date system, i.e. uncheck \u201cUse the 1904 date system\u201d option.) The returned datetime will have its timezone set to the tz argument, or the document\u2019s default timezone when tz is omitted or None. >>> XL_TO_DATE(41100.1875) datetime.datetime(2012, 7, 10, 4, 30, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(39448) datetime.datetime(2008, 1, 1, 0, 0, tzinfo=moment.tzinfo('America/New_York')) >>> XL_TO_DATE(40982.0625) datetime.datetime(2012, 3, 14, 1, 30, tzinfo=moment.tzinfo('America/New_York'))","title":"XL_TO_DATE"},{"location":"functions/#year","text":"Returns the year corresponding to a date as an integer. Same as date.year . >>> YEAR(DATE(2011, 4, 15)) 2011 >>> YEAR(\"5/31/2030\") 2030 >>> YEAR(datetime.datetime(1900, 1, 1)) 1900","title":"YEAR"},{"location":"functions/#yearfrac","text":"Calculates the fraction of the year represented by the number of whole days between two dates. Basis is the type of day count basis to use. 0 (default) - US (NASD) 30/360 1 - Actual/actual 2 - Actual/360 3 - Actual/365 4 - European 30/360 -1 - Actual/actual (Google Sheets variation) This function is useful for financial calculations. For compatibility with Excel, it defaults to using the NASD standard calendar. For use in non-financial settings, option -1 is likely the best choice. See https://en.wikipedia.org/wiki/360-day_calendar for explanation of the US 30/360 and European 30/360 methods. See for analysis of Excel\u2019s particular implementation. Basis -1 is similar to 1 , but differs from Excel when dates span both leap and non-leap years. It matches the calculation in Google Sheets, counting the days in each year as a fraction of that year\u2019s length. Fraction of the year between 1/1/2012 and 7/30/12, omitting the Basis argument. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30)) '0.58055556' Fraction between same dates, using the Actual/Actual basis argument. Because 2012 is a Leap year, it has a 366 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 1) '0.57650273' Fraction between same dates, using the Actual/365 basis argument. Uses a 365 day basis. >>> \"%.8f\" % YEARFRAC(DATE(2012, 1, 1), DATE(2012, 7, 30), 3) '0.57808219'","title":"YEARFRAC"},{"location":"functions/#info","text":"","title":"Info"},{"location":"functions/#cell","text":"Returns the requested information about the specified cell. This is not implemented in Grist Note This function is not currently implemented in Grist.","title":"CELL"},{"location":"functions/#isblank","text":"Returns whether a value refers to an empty cell. It isn\u2019t implemented in Grist. To check for an empty string, use value == \"\" . Note This function is not currently implemented in Grist.","title":"ISBLANK"},{"location":"functions/#isemail","text":"Returns whether a value is a valid email address. Note that checking email validity is not an exact science. The technical standard considers many email addresses valid that are not used in practice, and would not be considered valid by most users. Instead, we follow Google Sheets implementation, with some differences, noted below. >>> ISEMAIL(\"Abc.123@example.com\") True >>> ISEMAIL(\"Bob_O-Reilly+tag@example.com\") True >>> ISEMAIL(\"John Doe\") False >>> ISEMAIL(\"john@aol...com\") False","title":"ISEMAIL"},{"location":"functions/#iserr","text":"Checks whether a value is an error. In other words, it returns true if using value directly would raise an exception. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. A more Pythonic approach to checking for errors is: try: ... value ... except Exception, err: ... do something about the error ... For example: >>> ISERR(\"Hello\") False","title":"ISERR"},{"location":"functions/#iserror","text":"Checks whether a value is an error or an invalid value. It is similar to ISERR , but also returns true for an invalid value such as NaN or a text value in a Numeric column. NOTE: Grist implements this by automatically wrapping the argument to use lazy evaluation. >>> ISERROR(\"Hello\") False >>> ISERROR(AltText(\"fail\")) True >>> ISERROR(float('nan')) True","title":"ISERROR"},{"location":"functions/#islogical","text":"Checks whether a value is True or False . >>> ISLOGICAL(True) True >>> ISLOGICAL(False) True >>> ISLOGICAL(0) False >>> ISLOGICAL(None) False >>> ISLOGICAL(\"Test\") False","title":"ISLOGICAL"},{"location":"functions/#isna","text":"Checks whether a value is the error #N/A . >>> ISNA(float('nan')) True >>> ISNA(0.0) False >>> ISNA('text') False >>> ISNA(float('-inf')) False","title":"ISNA"},{"location":"functions/#isnontext","text":"Checks whether a value is non-textual. >>> ISNONTEXT(\"asdf\") False >>> ISNONTEXT(\"\") False >>> ISNONTEXT(AltText(\"text\")) False >>> ISNONTEXT(17.0) True >>> ISNONTEXT(None) True >>> ISNONTEXT(datetime.date(2011, 1, 1)) True","title":"ISNONTEXT"},{"location":"functions/#isnumber","text":"Checks whether a value is a number. >>> ISNUMBER(17) True >>> ISNUMBER(-123.123423) True >>> ISNUMBER(False) True >>> ISNUMBER(float('nan')) True >>> ISNUMBER(float('inf')) True >>> ISNUMBER('17') False >>> ISNUMBER(None) False >>> ISNUMBER(datetime.date(2011, 1, 1)) False","title":"ISNUMBER"},{"location":"functions/#isref","text":"Checks whether a value is a table record. For example, if a column person is of type Reference to the People table, then ISREF($person) is True . Similarly, ISREF(People.lookupOne(name=$name)) is True . For any other type of value, ISREF() would evaluate to False . >>> ISREF(17) False >>> ISREF(\"Roger\") False","title":"ISREF"},{"location":"functions/#isreflist","text":"Checks whether a value is a RecordSet , the type of values in Reference List columns. For example, if a column people is of type Reference List to the People table, then ISREFLIST($people) is True . Similarly, ISREFLIST(People.lookupRecords(name=$name)) is True . For any other type of value, ISREFLIST() would evaluate to False . >>> ISREFLIST(17) False >>> ISREFLIST(\"Roger\") False","title":"ISREFLIST"},{"location":"functions/#istext","text":"Checks whether a value is text. >>> ISTEXT(\"asdf\") True >>> ISTEXT(\"\") True >>> ISTEXT(AltText(\"text\")) True >>> ISTEXT(17.0) False >>> ISTEXT(None) False >>> ISTEXT(datetime.date(2011, 1, 1)) False","title":"ISTEXT"},{"location":"functions/#isurl","text":"Checks whether a value is a valid URL. It does not need to be fully qualified, or to include \u201chttp://\u201d and \u201cwww\u201d. It does not follow a standard, but attempts to work similarly to ISURL in Google Sheets, and to return True for text that is likely a URL. Valid protocols include ftp, http, https, gopher, mailto, news, telnet, and aim. >>> ISURL(\"http://www.getgrist.com\") True >>> ISURL(\"https://foo.com/test_(wikipedia)#cite-1\") True >>> ISURL(\"mailto://user@example.com\") True >>> ISURL(\"http:///a\") False","title":"ISURL"},{"location":"functions/#n","text":"Returns the value converted to a number. True/False are converted to 1/0. A date is converted to Excel-style serial number of the date. Anything else is converted to 0. >>> N(7) 7 >>> N(7.1) 7.1 >>> N(\"Even\") 0 >>> N(\"7\") 0 >>> N(True) 1 >>> N(datetime.datetime(2011, 4, 17)) 40650.0","title":"N"},{"location":"functions/#na","text":"Returns the \u201cvalue not available\u201d error, #N/A . >>> math.isnan(NA()) True","title":"NA"},{"location":"functions/#peek","text":"Evaluates the given expression without creating dependencies or requiring that referenced values are up to date, using whatever value it finds in a cell. This is useful for preventing circular reference errors, particularly in trigger formulas. For example, if the formula for A depends on $B and the formula for B depends on $A , then normally this would raise a circular reference error because each value needs to be calculated before the other. But if A uses PEEK($B) then it will simply get the value already stored in $B without requiring that $B is first calculated to the latest value. Therefore A will be calculated first, and B can use $A without problems.","title":"PEEK"},{"location":"functions/#record_2","text":"Returns a Python dictionary with all fields in the given record. If a list of records is given, returns a list of corresponding Python dictionaries. If dates_as_iso is set, Date and DateTime values are converted to string using ISO 8601 format. If expand_refs is set to 1 or higher, Reference values are replaced with a RECORD representation of the referenced record, expanding the given number of levels. Error values present in cells of the record are replaced with None value, and a special key of \u201c error \u201d gets added containing the error messages for those cells. For example: {\"Ratio\": None, \"_error_\": {\"Ratio\": \"ZeroDivisionError: integer division or modulo by zero\"}} Note that care is needed to avoid circular references when using RECORD(), since it creates a dependency on every cell in the record. In case of RECORD(rec), the cell containing this call will be omitted from the resulting dictionary. For example: RECORD($Person) RECORD(rec) RECORD(People.lookupOne(First_Name=\"Alice\")) RECORD(People.lookupRecords(Department=\"HR\"))","title":"RECORD"},{"location":"functions/#request","text":"Note This function is not currently implemented in Grist.","title":"REQUEST"},{"location":"functions/#type","text":"Returns a number associated with the type of data passed into the function. This is not implemented in Grist. Use isinstance(value, type) or type(value) . Note This function is not currently implemented in Grist.","title":"TYPE"},{"location":"functions/#logical","text":"","title":"Logical"},{"location":"functions/#and","text":"Returns True if all of the arguments are logically true, and False if any are false. Same as all([value1, value2, ...]) . >>> AND(1) True >>> AND(0) False >>> AND(1, 1) True >>> AND(1,2,3,4) True >>> AND(1,2,3,4,0) False","title":"AND"},{"location":"functions/#false","text":"Returns the logical value False . You may also use the value False directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> FALSE() False","title":"FALSE"},{"location":"functions/#if","text":"Returns one value if a logical expression is True and another if it is False . The equivalent Python expression is: value_if_true if logical_expression else value_if_false Since Grist supports multi-line formulas, you may also use Python blocks such as: if logical_expression: return value_if_true else: return value_if_false NOTE: Grist follows Excel model by only evaluating one of the value expressions, by automatically wrapping the expressions to use lazy evaluation. This allows IF(False, 1/0, 1) to evaluate to 1 rather than raise an exception. >>> IF(12, \"Yes\", \"No\") 'Yes' >>> IF(None, \"Yes\", \"No\") 'No' >>> IF(True, 0.85, 0.0) 0.85 >>> IF(False, 0.85, 0.0) 0.0","title":"IF"},{"location":"functions/#iferror","text":"Returns the first argument if it is not an error value, otherwise returns the second argument if present, or a blank if the second argument is absent. NOTE: Grist handles values that raise an exception by wrapping them to use lazy evaluation. >>> IFERROR(float('nan'), \"**NAN**\") '**NAN**' >>> IFERROR(17.17, \"**NAN**\") 17.17 >>> IFERROR(\"Text\") 'Text' >>> IFERROR(AltText(\"hello\")) ''","title":"IFERROR"},{"location":"functions/#not","text":"True . Same as not logical_expression . >>> NOT(123) False >>> NOT(0) True","title":"NOT"},{"location":"functions/#or","text":"Returns True if any of the arguments is logically true, and false if all of the arguments are false. Same as any([value1, value2, ...]) . >>> OR(1) True >>> OR(0) False >>> OR(1, 1) True >>> OR(0, 1) True >>> OR(0, 0) False >>> OR(0,False,0.0,\"\",None) False >>> OR(0,None,3,0) True","title":"OR"},{"location":"functions/#true","text":"Returns the logical value True . You may also use the value True directly. This function is provided primarily for compatibility with other spreadsheet programs. >>> TRUE() True","title":"TRUE"},{"location":"functions/#lookup_1","text":"","title":"Lookup"},{"location":"functions/#lookupone_2","text":"Returns a Record matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ). For example: People.lookupOne(First_Name=\"Lewis\", Last_Name=\"Carroll\") People.lookupOne(Email=$Work_Email) Learn more about lookupOne . If multiple records are found, the first match is returned. You may set the optional order_by parameter to the column ID by which to sort the matches, to determine which of them is returned as the first one. By default, the record with the lowest row ID is returned. See lookupRecords for details of all available options and behavior of order_by (and of its legacy alternative, sort_by ). For example: Tasks.lookupOne(Project=$id, order_by=\"Priority\") # Task with the smallest Priority. Rates.lookupOne(Person=$id, order_by=\"-Date\") # Rate with the latest Date.","title":"lookupOne"},{"location":"functions/#lookuprecords_2","text":"Returns a RecordSet matching the given field=value arguments. The value may be any expression, most commonly a field in the current row (e.g. $SomeField ) or a constant (e.g. a quoted string like \"Some Value\" ) (examples below). For example: People.lookupRecords(Email=$Work_Email) People.lookupRecords(First_Name=\"George\", Last_Name=\"Washington\") You may set the optional order_by parameter to the column ID by which to sort the results. You can prefix the column ID with \u201c-\u201d to reverse the order. You can also specify multiple column IDs as a tuple (e.g. order_by=(\"Account\", \"-Date\") ). For example: Transactions.lookupRecords(Account=$Account, order_by=\"Date\") Transactions.lookupRecords(Account=$Account, order_by=\"-Date\") Transactions.lookupRecords(Active=True, order_by=(\"Account\", \"-Date\")) For records with equal order_by fields, the results are sorted according to how they appear in views (which is determined by the special manualSort column). You may set order_by=None to match the order of records in unsorted views. By default, with no order_by , records are sorted by row ID, as if with order_by=\"id\" . For backward compatibility, sort_by may be used instead of order_by , but only allows a single field, and falls back to row ID (rather than manualSort ). See RecordSet for useful properties offered by the returned object. In particular, methods like .find.le allow searching for nearest values. See CONTAINS for an example utilizing UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. Learn more about lookupRecords .","title":"lookupRecords"},{"location":"functions/#address","text":"Returns a cell reference as a string. Note This function is not currently implemented in Grist.","title":"ADDRESS"},{"location":"functions/#choose","text":"Returns an element from a list of choices based on index. Note This function is not currently implemented in Grist.","title":"CHOOSE"},{"location":"functions/#column","text":"Returns the column number of a specified cell, with A=1 . Note This function is not currently implemented in Grist.","title":"COLUMN"},{"location":"functions/#columns","text":"Returns the number of columns in a specified array or range. Note This function is not currently implemented in Grist.","title":"COLUMNS"},{"location":"functions/#contains","text":"Use this marker with UserTable.lookupRecords to find records where a field of a list type (such as Choice List or Reference List ) contains the given value. For example: MoviesTable.lookupRecords(genre=CONTAINS(\"Drama\")) will return records in MoviesTable where the column genre is a list or other container such as [\"Comedy\", \"Drama\"] , i.e. \"Drama\" in $genre . Note that the column being looked up (e.g. genre ) must have values of a container type such as list, tuple, or set. In particular the values mustn\u2019t be strings, e.g. \"Comedy-Drama\" won\u2019t match even though \"Drama\" in \"Comedy-Drama\" is True in Python. It also won\u2019t match substrings within container elements, e.g. [\"Comedy-Drama\"] . You can optionally pass a second argument match_empty to indicate a value that should be matched against empty lists in the looked up column. For example, given this formula: MoviesTable.lookupRecords(genre=CONTAINS(g, match_empty='')) If g is '' (i.e. equal to match_empty ) then the column genre in the returned records will either be an empty list (or other container) or a list containing g as usual.","title":"CONTAINS"},{"location":"functions/#getpivotdata","text":"Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings. Note This function is not currently implemented in Grist.","title":"GETPIVOTDATA"},{"location":"functions/#hlookup","text":"Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found. Note This function is not currently implemented in Grist.","title":"HLOOKUP"},{"location":"functions/#hyperlink","text":"Creates a hyperlink inside a cell. Note This function is not currently implemented in Grist.","title":"HYPERLINK"},{"location":"functions/#index","text":"Returns the content of a cell, specified by row and column offset. Note This function is not currently implemented in Grist.","title":"INDEX"},{"location":"functions/#indirect","text":"Returns a cell reference specified by a string. Note This function is not currently implemented in Grist.","title":"INDIRECT"},{"location":"functions/#lookup","text":"Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column. Note This function is not currently implemented in Grist.","title":"LOOKUP"},{"location":"functions/#match","text":"Returns the relative position of an item in a range that matches a specified value. Note This function is not currently implemented in Grist.","title":"MATCH"},{"location":"functions/#offset","text":"Returns a range reference shifted a specified number of rows and columns from a starting cell reference. Note This function is not currently implemented in Grist.","title":"OFFSET"},{"location":"functions/#row","text":"Returns the row number of a specified cell. Note This function is not currently implemented in Grist.","title":"ROW"},{"location":"functions/#rows","text":"Returns the number of rows in a specified array or range. Note This function is not currently implemented in Grist.","title":"ROWS"},{"location":"functions/#self_hyperlink","text":"Creates a link to the current document. All parameters are optional. The returned string is in URL format, optionally preceded by a label and a space (the format expected for Grist Text columns with the HyperLink option enabled). A numeric page number can be supplied, which will create a link to the specified page. To find the numeric page number you need, visit a page and examine its URL for a /p/NN part. Any number of arguments of the form LinkKey_NAME may be provided, to set user.LinkKey.NAME values that will be available in access rules. For example, if a rule allows users to view rows when user.LinkKey.Code == rec.Code , we might want to create links with SELF_HYPERLINK(LinkKey_Code=$Code) . >>> SELF_HYPERLINK() u'https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(label='doc') u'doc https://docs.getgrist.com/sbaltsirg/Example' >>> SELF_HYPERLINK(page=2) u'https://docs.getgrist.com/sbaltsirg/Example/p/2' >>> SELF_HYPERLINK(LinkKey_Code='X1234') u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234' >>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo') u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo' >>> SELF_HYPERLINK(Linky_Link='Link') Traceback (most recent call last): ... TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)","title":"SELF_HYPERLINK"},{"location":"functions/#vlookup","text":"Vertical lookup. Searches the given table for a record matching the given field=value arguments. If multiple records match, returns one of them. If none match, returns the special empty record. The returned object is a record whose fields are available using .field syntax. For example, VLOOKUP(Employees, EmployeeID=$EmpID).Salary . Note that VLOOKUP isn\u2019t commonly needed in Grist, since Reference columns are the best way to link data between tables, and allow simple efficient usage such as $Person.Age . VLOOKUP is exactly quivalent to table.lookupOne(**field_value_pairs) . See lookupOne . For example: VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\") VLOOKUP(People, First_Name=\"Lewis\", Last_Name=\"Carroll\").Age","title":"VLOOKUP"},{"location":"functions/#math","text":"","title":"Math"},{"location":"functions/#abs","text":"Returns the absolute value of a number. >>> ABS(2) 2 >>> ABS(-2) 2 >>> ABS(-4) 4","title":"ABS"},{"location":"functions/#acos","text":"Returns the inverse cosine of a value, in radians. >>> round(ACOS(-0.5), 9) 2.094395102 >>> round(ACOS(-0.5)*180/PI(), 10) 120.0","title":"ACOS"},{"location":"functions/#acosh","text":"Returns the inverse hyperbolic cosine of a number. >>> ACOSH(1) 0.0 >>> round(ACOSH(10), 7) 2.9932228","title":"ACOSH"},{"location":"functions/#arabic","text":"Computes the value of a Roman numeral. >>> ARABIC(\"LVII\") 57 >>> ARABIC('mcmxii') 1912","title":"ARABIC"},{"location":"functions/#asin","text":"Returns the inverse sine of a value, in radians. >>> round(ASIN(-0.5), 9) -0.523598776 >>> round(ASIN(-0.5)*180/PI(), 10) -30.0 >>> round(DEGREES(ASIN(-0.5)), 10) -30.0","title":"ASIN"},{"location":"functions/#asinh","text":"Returns the inverse hyperbolic sine of a number. >>> round(ASINH(-2.5), 9) -1.647231146 >>> round(ASINH(10), 9) 2.99822295","title":"ASINH"},{"location":"functions/#atan","text":"Returns the inverse tangent of a value, in radians. >>> round(ATAN(1), 9) 0.785398163 >>> ATAN(1)*180/PI() 45.0 >>> DEGREES(ATAN(1)) 45.0","title":"ATAN"},{"location":"functions/#atan2","text":"Returns the angle between the x-axis and a line segment from the origin (0,0) to specified coordinate pair ( x , y ), in radians. >>> round(ATAN2(1, 1), 9) 0.785398163 >>> round(ATAN2(-1, -1), 9) -2.35619449 >>> ATAN2(-1, -1)*180/PI() -135.0 >>> DEGREES(ATAN2(-1, -1)) -135.0 >>> round(ATAN2(1,2), 9) 1.107148718","title":"ATAN2"},{"location":"functions/#atanh","text":"Returns the inverse hyperbolic tangent of a number. >>> round(ATANH(0.76159416), 9) 1.00000001 >>> round(ATANH(-0.1), 9) -0.100335348","title":"ATANH"},{"location":"functions/#ceiling","text":"Rounds a number up to the nearest multiple of factor, or the nearest integer if the factor is omitted or 1. >>> CEILING(2.5, 1) 3 >>> CEILING(-2.5, -2) -4 >>> CEILING(-2.5, 2) -2 >>> CEILING(1.5, 0.1) 1.5 >>> CEILING(0.234, 0.01) 0.24","title":"CEILING"},{"location":"functions/#combin","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects. >>> COMBIN(8,2) 28 >>> COMBIN(4,2) 6 >>> COMBIN(10,7) 120","title":"COMBIN"},{"location":"functions/#cos","text":"Returns the cosine of an angle provided in radians. >>> round(COS(1.047), 7) 0.5001711 >>> round(COS(60*PI()/180), 10) 0.5 >>> round(COS(RADIANS(60)), 10) 0.5","title":"COS"},{"location":"functions/#cosh","text":"Returns the hyperbolic cosine of any real number. >>> round(COSH(4), 6) 27.308233 >>> round(COSH(EXP(1)), 7) 7.6101251","title":"COSH"},{"location":"functions/#degrees","text":"Converts an angle value in radians to degrees. >>> round(DEGREES(ACOS(-0.5)), 10) 120.0 >>> DEGREES(PI()) 180.0","title":"DEGREES"},{"location":"functions/#even","text":"Rounds a number up to the nearest even integer, rounding away from zero. >>> EVEN(1.5) 2 >>> EVEN(3) 4 >>> EVEN(2) 2 >>> EVEN(-1) -2","title":"EVEN"},{"location":"functions/#exp","text":"Returns Euler\u2019s number, e (~2.718) raised to a power. >>> round(EXP(1), 8) 2.71828183 >>> round(EXP(2), 7) 7.3890561","title":"EXP"},{"location":"functions/#fact","text":"Returns the factorial of a number. >>> FACT(5) 120 >>> FACT(1.9) 1 >>> FACT(0) 1 >>> FACT(1) 1 >>> FACT(-1) Traceback (most recent call last): ... ValueError: factorial() not defined for negative values","title":"FACT"},{"location":"functions/#factdouble","text":"Returns the \u201cdouble factorial\u201d of a number. >>> FACTDOUBLE(6) 48 >>> FACTDOUBLE(7) 105 >>> FACTDOUBLE(3) 3 >>> FACTDOUBLE(4) 8","title":"FACTDOUBLE"},{"location":"functions/#floor","text":"Rounds a number down to the nearest integer multiple of specified significance. >>> FLOOR(3.7,2) 2 >>> FLOOR(-2.5,-2) -2 >>> FLOOR(2.5,-2) Traceback (most recent call last): ... ValueError: factor argument invalid >>> FLOOR(1.58,0.1) 1.5 >>> FLOOR(0.234,0.01) 0.23","title":"FLOOR"},{"location":"functions/#gcd","text":"Returns the greatest common divisor of one or more integers. >>> GCD(5, 2) 1 >>> GCD(24, 36) 12 >>> GCD(7, 1) 1 >>> GCD(5, 0) 5 >>> GCD(0, 5) 5 >>> GCD(5) 5 >>> GCD(14, 42, 21) 7","title":"GCD"},{"location":"functions/#int","text":"Rounds a number down to the nearest integer that is less than or equal to it. >>> INT(8.9) 8 >>> INT(-8.9) -9 >>> 19.5-INT(19.5) 0.5","title":"INT"},{"location":"functions/#lcm","text":"Returns the least common multiple of one or more integers. >>> LCM(5, 2) 10 >>> LCM(24, 36) 72 >>> LCM(0, 5) 0 >>> LCM(5) 5 >>> LCM(10, 100) 100 >>> LCM(12, 18) 36 >>> LCM(12, 18, 24) 72","title":"LCM"},{"location":"functions/#ln","text":"Returns the the logarithm of a number, base e (Euler\u2019s number). >>> round(LN(86), 7) 4.4543473 >>> round(LN(2.7182818), 7) 1.0 >>> round(LN(EXP(3)), 10) 3.0","title":"LN"},{"location":"functions/#log","text":"Returns the the logarithm of a number given a base. >>> LOG(10) 1.0 >>> LOG(8, 2) 3.0 >>> round(LOG(86, 2.7182818), 7) 4.4543473","title":"LOG"},{"location":"functions/#log10","text":"Returns the the logarithm of a number, base 10. >>> round(LOG10(86), 9) 1.934498451 >>> LOG10(10) 1.0 >>> LOG10(100000) 5.0 >>> LOG10(10**5) 5.0","title":"LOG10"},{"location":"functions/#mod","text":"Returns the result of the modulo operator, the remainder after a division operation. >>> MOD(3, 2) 1 >>> MOD(-3, 2) 1 >>> MOD(3, -2) -1 >>> MOD(-3, -2) -1","title":"MOD"},{"location":"functions/#mround","text":"Rounds one number to the nearest integer multiple of another. >>> MROUND(10, 3) 9 >>> MROUND(-10, -3) -9 >>> round(MROUND(1.3, 0.2), 10) 1.4 >>> MROUND(5, -2) Traceback (most recent call last): ... ValueError: factor argument invalid","title":"MROUND"},{"location":"functions/#multinomial","text":"Returns the factorial of the sum of values divided by the product of the values\u2019 factorials. >>> MULTINOMIAL(2, 3, 4) 1260 >>> MULTINOMIAL(3) 1 >>> MULTINOMIAL(1,2,3) 60 >>> MULTINOMIAL(0,2,4,6) 13860","title":"MULTINOMIAL"},{"location":"functions/#num","text":"For a Python floating-point value that\u2019s actually an integer, returns a Python integer type. Otherwise, returns the value unchanged. This is helpful sometimes when a value comes from a Numeric Grist column (represented as floats), but when int values are actually expected. >>> NUM(-17.0) -17 >>> NUM(1.5) 1.5 >>> NUM(4) 4 >>> NUM(\"NA\") 'NA'","title":"NUM"},{"location":"functions/#odd","text":"Rounds a number up to the nearest odd integer. >>> ODD(1.5) 3 >>> ODD(3) 3 >>> ODD(2) 3 >>> ODD(-1) -1 >>> ODD(-2) -3","title":"ODD"},{"location":"functions/#pi","text":"Returns the value of Pi to 14 decimal places. >>> round(PI(), 9) 3.141592654 >>> round(PI()/2, 9) 1.570796327 >>> round(PI()*9, 8) 28.27433388","title":"PI"},{"location":"functions/#power","text":"Returns a number raised to a power. >>> POWER(5,2) 25.0 >>> round(POWER(98.6,3.2), 3) 2401077.222 >>> round(POWER(4,5.0/4), 9) 5.656854249","title":"POWER"},{"location":"functions/#product","text":"Returns the result of multiplying a series of numbers together. Each argument may be a number or an array. >>> PRODUCT([5,15,30]) 2250 >>> PRODUCT([5,15,30], 2) 4500 >>> PRODUCT(5,15,[30],[2]) 4500","title":"PRODUCT"},{"location":"functions/#quotient","text":"Returns one number divided by another, without the remainder. >>> QUOTIENT(5, 2) 2 >>> QUOTIENT(4.5, 3.1) 1 >>> QUOTIENT(-10, 3) -3","title":"QUOTIENT"},{"location":"functions/#radians","text":"Converts an angle value in degrees to radians. >>> round(RADIANS(270), 6) 4.712389","title":"RADIANS"},{"location":"functions/#rand","text":"Returns a random number between 0 inclusive and 1 exclusive.","title":"RAND"},{"location":"functions/#randbetween","text":"Returns a uniformly random integer between two values, inclusive.","title":"RANDBETWEEN"},{"location":"functions/#roman","text":"Formats a number in Roman numerals. The second argument is ignored in this implementation. >>> ROMAN(499,0) 'CDXCIX' >>> ROMAN(499.2,0) 'CDXCIX' >>> ROMAN(57) 'LVII' >>> ROMAN(1912) 'MCMXII'","title":"ROMAN"},{"location":"functions/#round","text":"Rounds a number to a certain number of decimal places, by default to the nearest whole number if the number of places is not given. Rounds away from zero (\u2018up\u2019 for positive numbers) in the case of a tie, i.e. when the last digit is 5. >>> ROUND(1.4) 1.0 >>> ROUND(1.5) 2.0 >>> ROUND(2.5) 3.0 >>> ROUND(-2.5) -3.0 >>> ROUND(2.15, 1) 2.2 >>> ROUND(-1.475, 2) -1.48 >>> ROUND(21.5, -1) 20.0 >>> ROUND(626.3,-3) 1000.0 >>> ROUND(1.98,-1) 0.0 >>> ROUND(-50.55,-2) -100.0 >>> ROUND(0) 0.0","title":"ROUND"},{"location":"functions/#rounddown","text":"Rounds a number to a certain number of decimal places, always rounding down towards zero. >>> ROUNDDOWN(3.2, 0) 3 >>> ROUNDDOWN(76.9,0) 76 >>> ROUNDDOWN(3.14159, 3) 3.141 >>> ROUNDDOWN(-3.14159, 1) -3.1 >>> ROUNDDOWN(31415.92654, -2) 31400","title":"ROUNDDOWN"},{"location":"functions/#roundup","text":"Rounds a number to a certain number of decimal places, always rounding up away from zero. >>> ROUNDUP(3.2,0) 4 >>> ROUNDUP(76.9,0) 77 >>> ROUNDUP(3.14159, 3) 3.142 >>> ROUNDUP(-3.14159, 1) -3.2 >>> ROUNDUP(31415.92654, -2) 31500","title":"ROUNDUP"},{"location":"functions/#seriessum","text":"Given parameters x, n, m, and a, returns the power series sum a_1 x^n + a_2 x^(n+m) + \u2026 + a_i*x^(n+(i-1)m), where i is the number of entries in range a . >>> SERIESSUM(1,0,1,1) 1 >>> SERIESSUM(2,1,0,[1,2,3]) 12 >>> SERIESSUM(-3,1,1,[2,4,6]) -132 >>> round(SERIESSUM(PI()/4,0,2,[1,-1./FACT(2),1./FACT(4),-1./FACT(6)]), 6) 0.707103","title":"SERIESSUM"},{"location":"functions/#sign","text":"Given an input number, returns -1 if it is negative, 1 if positive, and 0 if it is zero. >>> SIGN(10) 1 >>> SIGN(4.0-4.0) 0 >>> SIGN(-0.00001) -1","title":"SIGN"},{"location":"functions/#sin","text":"Returns the sine of an angle provided in radians. >>> round(SIN(PI()), 10) 0.0 >>> SIN(PI()/2) 1.0 >>> round(SIN(30*PI()/180), 10) 0.5 >>> round(SIN(RADIANS(30)), 10) 0.5","title":"SIN"},{"location":"functions/#sinh","text":"Returns the hyperbolic sine of any real number. >>> round(2.868*SINH(0.0342*1.03), 7) 0.1010491","title":"SINH"},{"location":"functions/#sqrt","text":"Returns the positive square root of a positive number. >>> SQRT(16) 4.0 >>> SQRT(-16) Traceback (most recent call last): ... ValueError: math domain error >>> SQRT(ABS(-16)) 4.0","title":"SQRT"},{"location":"functions/#sqrtpi","text":"Returns the positive square root of the product of Pi and the given positive number. >>> round(SQRTPI(1), 6) 1.772454 >>> round(SQRTPI(2), 6) 2.506628","title":"SQRTPI"},{"location":"functions/#subtotal","text":"Returns a subtotal for a vertical range of cells using a specified aggregation function. Note This function is not currently implemented in Grist.","title":"SUBTOTAL"},{"location":"functions/#sum","text":"Returns the sum of a series of numbers. Each argument may be a number or an array. Non-numeric values are ignored. >>> SUM([5,15,30]) 50 >>> SUM([5.,15,30], 2) 52.0 >>> SUM(5,15,[30],[2]) 52","title":"SUM"},{"location":"functions/#sumif","text":"Returns a conditional sum across a range. Note This function is not currently implemented in Grist.","title":"SUMIF"},{"location":"functions/#sumifs","text":"Returns the sum of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"SUMIFS"},{"location":"functions/#sumproduct","text":"Multiplies corresponding components in two equally-sized arrays, and returns the sum of those products. >>> SUMPRODUCT([3,8,1,4,6,9], [2,6,5,7,7,3]) 156 >>> SUMPRODUCT([], [], []) 0 >>> SUMPRODUCT([-0.25], [-2], [-3]) -1.5 >>> SUMPRODUCT([-0.25, -0.25], [-2, -2], [-3, -3]) -3.0","title":"SUMPRODUCT"},{"location":"functions/#sumsq","text":"Returns the sum of the squares of a series of numbers and/or cells. Note This function is not currently implemented in Grist.","title":"SUMSQ"},{"location":"functions/#tan","text":"Returns the tangent of an angle provided in radians. >>> round(TAN(0.785), 8) 0.99920399 >>> round(TAN(45*PI()/180), 10) 1.0 >>> round(TAN(RADIANS(45)), 10) 1.0","title":"TAN"},{"location":"functions/#tanh","text":"Returns the hyperbolic tangent of any real number. >>> round(TANH(-2), 6) -0.964028 >>> TANH(0) 0.0 >>> round(TANH(0.5), 6) 0.462117","title":"TANH"},{"location":"functions/#trunc","text":"Truncates a number to a certain number of significant digits by omitting less significant digits. >>> TRUNC(8.9) 8 >>> TRUNC(-8.9) -8 >>> TRUNC(0.45) 0","title":"TRUNC"},{"location":"functions/#uuid","text":"Generate a random UUID-formatted string identifier. Since UUID() produces a different value each time it\u2019s called, it is best to use it in trigger formula for new records. This would only calculate UUID() once and freeze the calculated value. By contrast, a regular formula may get recalculated any time the document is reloaded, producing a different value for UUID() each time.","title":"UUID"},{"location":"functions/#schedule_1","text":"","title":"Schedule"},{"location":"functions/#schedule","text":"Returns the list of datetime objects generated according to the schedule string. Starts at start , which defaults to NOW(). Generates at most count results (10 by default). If end is given, stops there. The schedule has the format \u201cINTERVAL: SLOTS, \u2026\u201d. For example: annual: Jan-15, Apr-15, Jul-15 -- Three times a year on given dates at midnight. annual: 1/15, 4/15, 7/15 -- Same as above. monthly: /1 2pm, /15 2pm -- The 1st and the 15th of each month, at 2pm. 3-months: /10, +1m /20 -- Every 3 months on the 10th of month 1, 20th of month 2. weekly: Mo 9am, Tu 9am, Fr 2pm -- Three times a week at specified times. 2-weeks: Mo, +1w Tu -- Every 2 weeks on Monday of week 1, Tuesday of week 2. daily: 07:30, 21:00 -- Twice a day at specified times. 2-day: 12am, 4pm, +1d 8am -- Three times every two days, evenly spaced. hourly: :15, :45 -- 15 minutes before and after each hour. 4-hour: :00, 1:20, 2:40 -- Three times every 4 hours, evenly spaced. 10-minute: +0s -- Every 10 minutes on the minute. INTERVAL must be either of the form N-unit where N is a number and unit is one of year , month , week , day , hour ; or one of the aliases: annual , monthly , weekly , daily , hourly , which mean 1-year , 1-month , etc. SLOTS support the following units: `Jan-15` or `1/15` -- Month and day of the month; available when INTERVAL is year-based. `/15` -- Day of the month, available when INTERVAL is month-based. `Mon`, `Mo`, `Friday` -- Day of the week (or abbreviation), when INTERVAL is week-based. 10am, 1:30pm, 15:45 -- Time of day, available for day-based or longer intervals. :45, :00 -- Minutes of the hour, available when INTERVAL is hour-based. +1d, +15d -- How many days to add to start of INTERVAL. +1w -- How many weeks to add to start of INTERVAL. +1m -- How many months to add to start of INTERVAL. The SLOTS are always relative to the INTERVAL rather than to start . Week-based intervals start on Sunday. E.g. weekly: +1d, +4d is the same as weekly: Mon, Thu , and generates times on Mondays and Thursdays regardless of start . The first generated time is determined by the unit of the INTERVAL without regard to the multiple. E.g. both \u201c2-week: Mon\u201d and \u201c3-week: Mon\u201d start on the first Monday after start , and then generate either every second or every third Monday after that. Similarly, 24-hour: :00 starts with the first top-of-the-hour after start (not with midnight), and then repeats every 24 hours. To start with the midnight after start , use daily: 0:00 . For interval units of a day or longer, if time-of-day is not specified, it defaults to midnight. The time zone of start determines the time zone of the generated times. >>> def show(dates): return [d.strftime(\"%Y-%m-%d %H:%M\") for d in dates] >>> start = datetime(2018, 9, 4, 14, 0); # 2pm on Tue, Sep 4 2018. >>> show(SCHEDULE('annual: Jan-15, Apr-15, Jul-15, Oct-15', start=start, count=4)) ['2018-10-15 00:00', '2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00'] >>> show(SCHEDULE('annual: 1/15, 4/15, 7/15', start=start, count=4)) ['2019-01-15 00:00', '2019-04-15 00:00', '2019-07-15 00:00', '2020-01-15 00:00'] >>> show(SCHEDULE('monthly: /1 2pm, /15 5pm', start=start, count=4)) ['2018-09-15 17:00', '2018-10-01 14:00', '2018-10-15 17:00', '2018-11-01 14:00'] >>> show(SCHEDULE('3-months: /10, +1m /20', start=start, count=4)) ['2018-09-10 00:00', '2018-10-20 00:00', '2018-12-10 00:00', '2019-01-20 00:00'] >>> show(SCHEDULE('weekly: Mo 9am, Tu 9am, Fr 2pm', start=start, count=4)) ['2018-09-07 14:00', '2018-09-10 09:00', '2018-09-11 09:00', '2018-09-14 14:00'] >>> show(SCHEDULE('2-weeks: Mo, +1w Tu', start=start, count=4)) ['2018-09-11 00:00', '2018-09-17 00:00', '2018-09-25 00:00', '2018-10-01 00:00'] >>> show(SCHEDULE('daily: 07:30, 21:00', start=start, count=4)) ['2018-09-04 21:00', '2018-09-05 07:30', '2018-09-05 21:00', '2018-09-06 07:30'] >>> show(SCHEDULE('2-day: 12am, 4pm, +1d 8am', start=start, count=4)) ['2018-09-04 16:00', '2018-09-05 08:00', '2018-09-06 00:00', '2018-09-06 16:00'] >>> show(SCHEDULE('hourly: :15, :45', start=start, count=4)) ['2018-09-04 14:15', '2018-09-04 14:45', '2018-09-04 15:15', '2018-09-04 15:45'] >>> show(SCHEDULE('4-hour: :00, +1H :20, +2H :40', start=start, count=4)) ['2018-09-04 14:00', '2018-09-04 15:20', '2018-09-04 16:40', '2018-09-04 18:00']","title":"SCHEDULE"},{"location":"functions/#stats","text":"","title":"Stats"},{"location":"functions/#avedev","text":"Calculates the average of the magnitudes of deviations of data from a dataset\u2019s mean. Note This function is not currently implemented in Grist.","title":"AVEDEV"},{"location":"functions/#average","text":"Returns the numerical average value in a dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. >>> AVERAGE([2, -1.0, 11]) 4.0 >>> AVERAGE([2, -1, 11, \"Hello\"]) 4.0 >>> AVERAGE([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11]) 4.0 >>> AVERAGE(False, True) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"AVERAGE"},{"location":"functions/#averagea","text":"Returns the numerical average value in a dataset, counting non-numerical values as 0. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. >>> AVERAGEA([2, -1.0, 11]) 4.0 >>> AVERAGEA([2, -1, 11, \"Hello\"]) 3.0 >>> AVERAGEA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 1.5 >>> AVERAGEA(False, True) 0.5","title":"AVERAGEA"},{"location":"functions/#averageif","text":"Returns the average of a range depending on criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIF"},{"location":"functions/#averageifs","text":"Returns the average of a range depending on multiple criteria. Note This function is not currently implemented in Grist.","title":"AVERAGEIFS"},{"location":"functions/#average_weighted","text":"Given a list of (value, weight) pairs, finds the average of the values weighted by the corresponding weights. Ignores any pairs with a non-numerical value or weight. If you have two lists, of values and weights, use the Python built-in zip() function to create a list of pairs. >>> AVERAGE_WEIGHTED(((95, .25), (90, .1), (\"X\", .5), (85, .15), (88, .2), (82, .3), (70, None))) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, \"X\", 85, 88, 82, 70], [25, 10, 50, 15, 20, 30, None])) 87.7 >>> AVERAGE_WEIGHTED(zip([95, 90, False, 85, 88, 82, 70], [.25, .1, .5, .15, .2, .3, True])) 87.7","title":"AVERAGE_WEIGHTED"},{"location":"functions/#binomdist","text":"Calculates the probability of drawing a certain number of successes (or a maximum number of successes) in a certain number of tries given a population of a certain size containing a certain number of successes, with replacement of draws. Note This function is not currently implemented in Grist.","title":"BINOMDIST"},{"location":"functions/#confidence","text":"Calculates the width of half the confidence interval for a normal distribution. Note This function is not currently implemented in Grist.","title":"CONFIDENCE"},{"location":"functions/#correl","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"CORREL"},{"location":"functions/#count","text":"Returns the count of numerical and date/datetime values in a dataset, ignoring other types of values. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. >>> COUNT([2, -1.0, 11]) 3 >>> COUNT([2, -1, 11, \"Hello\"]) 3 >>> COUNT([DATE(2000, 1, 1), DATE(2000, 1, 2), DATE(2000, 1, 3), \"Hello\"]) 3 >>> COUNT([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 4 >>> COUNT(False, True) 0","title":"COUNT"},{"location":"functions/#counta","text":"Returns the count of all values in a dataset, including non-numerical values. Each argument may be a value or an array. >>> COUNTA([2, -1.0, 11]) 3 >>> COUNTA([2, -1, 11, \"Hello\"]) 4 >>> COUNTA([2, -1, \"Hello\", DATE(2015,1,1)], True, [False, \"123\", \"\", 11.5]) 9 >>> COUNTA(False, True) 2","title":"COUNTA"},{"location":"functions/#covar","text":"Calculates the covariance of a dataset. Note This function is not currently implemented in Grist.","title":"COVAR"},{"location":"functions/#critbinom","text":"Calculates the smallest value for which the cumulative binomial distribution is greater than or equal to a specified criteria. Note This function is not currently implemented in Grist.","title":"CRITBINOM"},{"location":"functions/#devsq","text":"Calculates the sum of squares of deviations based on a sample. Note This function is not currently implemented in Grist.","title":"DEVSQ"},{"location":"functions/#expondist","text":"Returns the value of the exponential distribution function with a specified lambda at a specified value. Note This function is not currently implemented in Grist.","title":"EXPONDIST"},{"location":"functions/#fdist","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"FDIST"},{"location":"functions/#fisher","text":"Returns the Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHER"},{"location":"functions/#fisherinv","text":"Returns the inverse Fisher transformation of a specified value. Note This function is not currently implemented in Grist.","title":"FISHERINV"},{"location":"functions/#forecast","text":"Calculates the expected y-value for a specified x based on a linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"FORECAST"},{"location":"functions/#f_dist","text":"Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST"},{"location":"functions/#f_dist_rt","text":"Calculates the right-tailed F probability distribution (degree of diversity) for two data sets with given input x. Alternately called Fisher-Snedecor distribution or Snedecor\u2019s F distribution. Note This function is not currently implemented in Grist.","title":"F_DIST_RT"},{"location":"functions/#geomean","text":"Calculates the geometric mean of a dataset. Note This function is not currently implemented in Grist.","title":"GEOMEAN"},{"location":"functions/#harmean","text":"Calculates the harmonic mean of a dataset. Note This function is not currently implemented in Grist.","title":"HARMEAN"},{"location":"functions/#hypgeomdist","text":"Calculates the probability of drawing a certain number of successes in a certain number of tries given a population of a certain size containing a certain number of successes, without replacement of draws. Note This function is not currently implemented in Grist.","title":"HYPGEOMDIST"},{"location":"functions/#intercept","text":"Calculates the y-value at which the line resulting from linear regression of a dataset will intersect the y-axis (x=0). Note This function is not currently implemented in Grist.","title":"INTERCEPT"},{"location":"functions/#kurt","text":"Calculates the kurtosis of a dataset, which describes the shape, and in particular the \u201cpeakedness\u201d of that dataset. Note This function is not currently implemented in Grist.","title":"KURT"},{"location":"functions/#large","text":"Returns the nth largest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"LARGE"},{"location":"functions/#loginv","text":"Returns the value of the inverse log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGINV"},{"location":"functions/#lognormdist","text":"Returns the value of the log-normal cumulative distribution with given mean and standard deviation at a specified value. Note This function is not currently implemented in Grist.","title":"LOGNORMDIST"},{"location":"functions/#max","text":"Returns the maximum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MAX([2, -1.5, 11.5]) 11.5 >>> MAX([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAX(True, -123) -123 >>> MAX(\"123\", -123) -123 >>> MAX(\"Hello\", \"123\", True, False) 0 >>> MAX(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 2) >>> MAX(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56) >>> MAX(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 2)","title":"MAX"},{"location":"functions/#maxa","text":"Returns the maximum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MAXA([2, -1.5, 11.5]) 11.5 >>> MAXA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) 11.5 >>> MAXA(True, -123) 1 >>> MAXA(\"123\", -123) 0 >>> MAXA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MAXA"},{"location":"functions/#median","text":"Returns the median value in a numeric dataset, ignoring non-numerical values. Each argument may be a value or an array. Values that are not numbers, including logical and blank values, and text representations of numbers, are ignored. Produces an error if the arguments contain no numbers. The median is the middle number when all values are sorted. So half of the values in the dataset are less than the median, and half of the values are greater. If there is an even number of values in the dataset, returns the average of the two numbers in the middle. >>> MEDIAN(1, 2, 3, 4, 5) 3 >>> MEDIAN(3, 5, 1, 4, 2) 3 >>> MEDIAN(range(10)) 4.5 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1), 12.3) 12.3 >>> MEDIAN(\"Hello\", \"123\", DATE(2015, 1, 1)) Traceback (most recent call last): ... ValueError: MEDIAN requires at least one number","title":"MEDIAN"},{"location":"functions/#min","text":"Returns the minimum value in a dataset, ignoring values other than numbers and dates/datetimes. Each argument may be a value or an array. Values that are not numbers or dates, including logical and blank values, and text representations of numbers, are ignored. Returns 0 if the arguments contain no numbers or dates. >>> MIN([2, -1.5, 11.5]) -1.5 >>> MIN([2, -1.5, \"Hello\"], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MIN(True, 123) 123 >>> MIN(\"-123\", 123) 123 >>> MIN(\"Hello\", \"123\", True, False) 0 >>> MIN(DATE(2015, 1, 1), DATE(2015, 1, 2)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 1), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.date(2015, 1, 1) >>> MIN(DATE(2015, 1, 2), datetime.datetime(2015, 1, 1, 12, 34, 56)) datetime.datetime(2015, 1, 1, 12, 34, 56)","title":"MIN"},{"location":"functions/#mina","text":"Returns the minimum numeric value in a dataset. Each argument may be a value of an array. Values that are not numbers, including dates and text representations of numbers, are counted as 0 (zero). Logical value of True is counted as 1, and False as 0. Returns 0 if the arguments contain no numbers. >>> MINA([2, -1.5, 11.5]) -1.5 >>> MINA([2, -1.5, \"Hello\", DATE(2015, 1, 1)], True, [False, \"123\", \"\", 11.5]) -1.5 >>> MINA(True, 123) 1 >>> MINA(\"-123\", 123) 0 >>> MINA(\"Hello\", \"123\", DATE(2015, 1, 1)) 0","title":"MINA"},{"location":"functions/#mode","text":"Returns the most commonly occurring value in a dataset. Note This function is not currently implemented in Grist.","title":"MODE"},{"location":"functions/#negbinomdist","text":"Calculates the probability of drawing a certain number of failures before a certain number of successes given a probability of success in independent trials. Note This function is not currently implemented in Grist.","title":"NEGBINOMDIST"},{"location":"functions/#normdist","text":"Returns the value of the normal distribution function (or normal cumulative distribution function) for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMDIST"},{"location":"functions/#norminv","text":"Returns the value of the inverse normal distribution function for a specified value, mean, and standard deviation. Note This function is not currently implemented in Grist.","title":"NORMINV"},{"location":"functions/#normsdist","text":"Returns the value of the standard normal cumulative distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSDIST"},{"location":"functions/#normsinv","text":"Returns the value of the inverse standard normal distribution function for a specified value. Note This function is not currently implemented in Grist.","title":"NORMSINV"},{"location":"functions/#pearson","text":"Calculates r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"PEARSON"},{"location":"functions/#percentile","text":"Returns the value at a given percentile of a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTILE"},{"location":"functions/#percentrank","text":"Returns the percentage rank (percentile) of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK"},{"location":"functions/#percentrank_exc","text":"Returns the percentage rank (percentile) from 0 to 1 exclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_EXC"},{"location":"functions/#percentrank_inc","text":"Returns the percentage rank (percentile) from 0 to 1 inclusive of a specified value in a dataset. Note This function is not currently implemented in Grist.","title":"PERCENTRANK_INC"},{"location":"functions/#permut","text":"Returns the number of ways to choose some number of objects from a pool of a given size of objects, considering order. Note This function is not currently implemented in Grist.","title":"PERMUT"},{"location":"functions/#poisson","text":"Returns the value of the Poisson distribution function (or Poisson cumulative distribution function) for a specified value and mean. Note This function is not currently implemented in Grist.","title":"POISSON"},{"location":"functions/#prob","text":"Given a set of values and corresponding probabilities, calculates the probability that a value chosen at random falls between two limits. Note This function is not currently implemented in Grist.","title":"PROB"},{"location":"functions/#quartile","text":"Returns a value nearest to a specified quartile of a dataset. Note This function is not currently implemented in Grist.","title":"QUARTILE"},{"location":"functions/#rank_avg","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the average rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_AVG"},{"location":"functions/#rank_eq","text":"Returns the rank of a specified value in a dataset. If there is more than one entry of the same value in the dataset, the top rank of the entries will be returned. Note This function is not currently implemented in Grist.","title":"RANK_EQ"},{"location":"functions/#rsq","text":"Calculates the square of r, the Pearson product-moment correlation coefficient of a dataset. Note This function is not currently implemented in Grist.","title":"RSQ"},{"location":"functions/#skew","text":"Calculates the skewness of a dataset, which describes the symmetry of that dataset about the mean. Note This function is not currently implemented in Grist.","title":"SKEW"},{"location":"functions/#slope","text":"Calculates the slope of the line resulting from linear regression of a dataset. Note This function is not currently implemented in Grist.","title":"SLOPE"},{"location":"functions/#small","text":"Returns the nth smallest element from a data set, where n is user-defined. Note This function is not currently implemented in Grist.","title":"SMALL"},{"location":"functions/#standardize","text":"Calculates the normalized equivalent of a random variable given mean and standard deviation of the distribution. Note This function is not currently implemented in Grist.","title":"STANDARDIZE"},{"location":"functions/#stdev","text":"Calculates the standard deviation based on a sample, ignoring non-numerical values. >>> STDEV([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.277849927241488 >>> STDEV([2, 5, 8, 13, 10], 3, 12, 15) 4.810702354423639 >>> STDEV([2, 5, 8, 13, 10], [3, 12, 15]) 4.810702354423639 >>> STDEV([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEV"},{"location":"functions/#stdeva","text":"Calculates the standard deviation based on a sample, setting text to the value 0 . >>> STDEVA([2, 5, 8, 13, 10]) 4.277849927241488 >>> STDEVA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], 1, 0, 0) 4.969550137731641 >>> STDEVA([2, 5, 8, 13, 10], [1, 0, 0]) 4.969550137731641 >>> STDEVA([5]) Traceback (most recent call last): ... ZeroDivisionError: float division by zero","title":"STDEVA"},{"location":"functions/#stdevp","text":"Calculates the standard deviation based on an entire population, ignoring non-numerical values. >>> STDEVP([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10, True, False, \"Test\"]) 3.8262252939417984 >>> STDEVP([2, 5, 8, 13, 10], 3, 12, 15) 4.5 >>> STDEVP([2, 5, 8, 13, 10], [3, 12, 15]) 4.5 >>> STDEVP([5]) 0.0","title":"STDEVP"},{"location":"functions/#stdevpa","text":"Calculates the standard deviation based on an entire population, setting text to the value 0 . >>> STDEVPA([2, 5, 8, 13, 10]) 3.8262252939417984 >>> STDEVPA([2, 5, 8, 13, 10, True, False, \"Test\"]) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], 1, 0, 0) 4.648588495446763 >>> STDEVPA([2, 5, 8, 13, 10], [1, 0, 0]) 4.648588495446763 >>> STDEVPA([5]) 0.0","title":"STDEVPA"},{"location":"functions/#steyx","text":"Calculates the standard error of the predicted y-value for each x in the regression of a dataset. Note This function is not currently implemented in Grist.","title":"STEYX"},{"location":"functions/#tdist","text":"Calculates the probability for Student\u2019s t-distribution with a given input (x). Note This function is not currently implemented in Grist.","title":"TDIST"},{"location":"functions/#tinv","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"TINV"},{"location":"functions/#trimmean","text":"Calculates the mean of a dataset excluding some proportion of data from the high and low ends of the dataset. Note This function is not currently implemented in Grist.","title":"TRIMMEAN"},{"location":"functions/#ttest","text":"Returns the probability associated with t-test. Determines whether two samples are likely to have come from the same two underlying populations that have the same mean. Note This function is not currently implemented in Grist.","title":"TTEST"},{"location":"functions/#t_inv","text":"Calculates the negative inverse of the one-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV"},{"location":"functions/#t_inv_2t","text":"Calculates the inverse of the two-tailed TDIST function. Note This function is not currently implemented in Grist.","title":"T_INV_2T"},{"location":"functions/#var","text":"Calculates the variance based on a sample. Note This function is not currently implemented in Grist.","title":"VAR"},{"location":"functions/#vara","text":"Calculates an estimate of variance based on a sample, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARA"},{"location":"functions/#varp","text":"Calculates the variance based on an entire population. Note This function is not currently implemented in Grist.","title":"VARP"},{"location":"functions/#varpa","text":"Calculates the variance based on an entire population, setting text to the value 0 . Note This function is not currently implemented in Grist.","title":"VARPA"},{"location":"functions/#weibull","text":"Returns the value of the Weibull distribution function (or Weibull cumulative distribution function) for a specified shape and scale. Note This function is not currently implemented in Grist.","title":"WEIBULL"},{"location":"functions/#ztest","text":"Returns the two-tailed P-value of a Z-test with standard distribution. Note This function is not currently implemented in Grist.","title":"ZTEST"},{"location":"functions/#text_1","text":"","title":"Text"},{"location":"functions/#char","text":"Convert a number into a character according to the current Unicode table. Same as unichr(number) . >>> CHAR(65) u'A' >>> CHAR(33) u'!'","title":"CHAR"},{"location":"functions/#clean","text":"Returns the text with the non-printable characters removed. This removes both characters with values 0 through 31, and other Unicode characters in the \u201ccontrol characters\u201d category. >>> CLEAN(CHAR(9) + \"Monthly report\" + CHAR(10)) u'Monthly report'","title":"CLEAN"},{"location":"functions/#code","text":"Returns the numeric Unicode map value of the first character in the string provided. Same as ord(string[0]) . >>> CODE(\"A\") 65 >>> CODE(\"!\") 33 >>> CODE(\"!A\") 33","title":"CODE"},{"location":"functions/#concat","text":"Joins together any number of text strings into one string. Also available under the name CONCATENATE . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCAT(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCAT(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCAT(\"abc\") u'abc' >>> CONCAT(0, \"abc\") u'0abc' >>> assert CONCAT(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCAT"},{"location":"functions/#concatenate","text":"Joins together any number of text strings into one string. Also available under the name CONCAT . Similar to the Python expression \"\".join(array_of_strings) . >>> CONCATENATE(\"Stream population for \", \"trout\", \" \", \"species\", \" is \", 32, \"/mile.\") u'Stream population for trout species is 32/mile.' >>> CONCATENATE(\"In \", 4, \" days it is \", datetime.date(2016,1,1)) u'In 4 days it is 2016-01-01' >>> CONCATENATE(\"abc\") u'abc' >>> CONCATENATE(0, \"abc\") u'0abc' >>> assert CONCATENATE(2, u\" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", u\"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e' >>> assert CONCATENATE(2, \" cr\u00e8me \", \"br\u00fbl\u00e9e\") == u'2 cr\u00e8me br\u00fbl\u00e9e'","title":"CONCATENATE"},{"location":"functions/#dollar","text":"Formats a number into a formatted dollar amount, with decimals rounded to the specified place (. If decimals value is omitted, it defaults to 2. >>> DOLLAR(1234.567) '$1,234.57' >>> DOLLAR(1234.567, -2) '$1,200' >>> DOLLAR(-1234.567, -2) '($1,200)' >>> DOLLAR(-0.123, 4) '($0.1230)' >>> DOLLAR(99.888) '$99.89' >>> DOLLAR(0) '$0.00' >>> DOLLAR(10, 0) '$10'","title":"DOLLAR"},{"location":"functions/#exact","text":"Tests whether two strings are identical. Same as string2 == string2 . >>> EXACT(\"word\", \"word\") True >>> EXACT(\"Word\", \"word\") False >>> EXACT(\"w ord\", \"word\") False","title":"EXACT"},{"location":"functions/#find","text":"Returns the position at which a string is first found within text. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> FIND(\"M\", \"Miriam McGovern\") 1 >>> FIND(\"m\", \"Miriam McGovern\") 6 >>> FIND(\"M\", \"Miriam McGovern\", 3) 8 >>> FIND(\" #\", \"Hello world # Test\") 12 >>> FIND(\"gle\", \"Google\", 1) 4 >>> FIND(\"GLE\", \"Google\", 1) Traceback (most recent call last): ... ValueError: substring not found >>> FIND(\"page\", \"homepage\") 5 >>> FIND(\"page\", \"homepage\", 6) Traceback (most recent call last): ... ValueError: substring not found","title":"FIND"},{"location":"functions/#fixed","text":"Formats a number with a fixed number of decimal places (2 by default), and commas. If no_commas is True, then omits the commas. >>> FIXED(1234.567, 1) '1,234.6' >>> FIXED(1234.567, -1) '1,230' >>> FIXED(-1234.567, -1, True) '-1230' >>> FIXED(44.332) '44.33' >>> FIXED(3521.478, 2, False) '3,521.48' >>> FIXED(-3521.478, 1, True) '-3521.5' >>> FIXED(3521.478, 0, True) '3521' >>> FIXED(3521.478, -2, True) '3500'","title":"FIXED"},{"location":"functions/#left","text":"Returns a substring of length num_chars from the beginning of the given string. If num_chars is omitted, it is assumed to be 1. Same as string[:num_chars] . >>> LEFT(\"Sale Price\", 4) 'Sale' >>> LEFT('Swededn') 'S' >>> LEFT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"LEFT"},{"location":"functions/#len","text":"Returns the number of characters in a text string, or the number of items in a list. Same as len in python. See Record Set for an example of using len on a list of records. >>> LEN(\"Phoenix, AZ\") 11 >>> LEN(\"\") 0 >>> LEN(\" One \") 11","title":"LEN"},{"location":"functions/#lower","text":"Converts a specified string to lowercase. Same as text.lower() . >>> LOWER(\"E. E. Cummings\") 'e. e. cummings' >>> LOWER(\"Apt. 2B\") 'apt. 2b'","title":"LOWER"},{"location":"functions/#mid","text":"Returns a segment of a string, starting at start_num. The first character in text has start_num 1. >>> MID(\"Fluid Flow\", 1, 5) 'Fluid' >>> MID(\"Fluid Flow\", 7, 20) 'Flow' >>> MID(\"Fluid Flow\", 20, 5) '' >>> MID(\"Fluid Flow\", 0, 5) Traceback (most recent call last): ... ValueError: start_num invalid","title":"MID"},{"location":"functions/#phone_format","text":"Formats a phone number. With no optional arguments, the number must start with \u201c+\u201d and the international dialing prefix, and will be formatted as an international number, e.g. +12345678901 becomes +1 234-567-8901 . The country argument allows specifying a 2-letter country code (e.g. \u201cUS\u201d or \u201cGB\u201d) for interpreting phone numbers that don\u2019t start with \u201c+\u201d. E.g. PHONE_FORMAT('2025555555', 'US') would be seen as a US number and formatted as \u201c(202) 555-5555\u201d. Phone numbers that start with \u201c+\u201d ignore country . E.g. PHONE_FORMAT('+33555555555', 'US') is a French number because \u2018+33\u2019 is the international prefix for France. The format argument specifies the output format, according to this table: \"#\" or \"NATL\" (default) - use the national format, without the international dialing prefix, when possible. E.g. (234) 567-8901 for \u201cUS\u201d, or 02 34 56 78 90 for \u201cFR\u201d. If country is omitted, or the number does not correspond to the given country, the international format is used instead. \"+\" or \"INTL\" - international format, e.g. +1 234-567-8901 or +33 2 34 56 78 90 . \"*\" or \"E164\" - E164 format, like international but with no separators, e.g. +12345678901 . \"tel\" or \"RFC3966\" - format suitable to use as a hyperlink , e.g. \u2018tel:+1-234-567-8901\u2019. When specifying the format argument, you may omit the country argument. I.e. PHONE_FORMAT(value, \"tel\") is equivalent to PHONE_FORMAT(value, None, \"tel\") . For more details, see the phonenumbers Python library, which underlies this function. >>> PHONE_FORMAT(\"+12345678901\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"2345678901\", \"US\") u'(234) 567-8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"2345678901\", \"GB\", \"+\") u'+44 23 4567 8901' >>> PHONE_FORMAT(\"+442345678901\", \"GB\") u'023 4567 8901' >>> PHONE_FORMAT(\"+12345678901\", \"GB\") u'+1 234-567-8901' >>> PHONE_FORMAT(\"(234) 567-8901\") Traceback (most recent call last): ... NumberParseException: (0) Missing or invalid default region. >>> PHONE_FORMAT(\"(234)567 89-01\", \"US\", \"tel\") u'tel:+1-234-567-8901' >>> PHONE_FORMAT(\"2/3456/7890\", \"FR\", '#') u'02 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", '#') u'+33 2 34 56 78 90' >>> PHONE_FORMAT(\"+33234567890\", 'tel') u'tel:+33-2-34-56-78-90' >>> PHONE_FORMAT(\"tel:+1-234-567-8901\", country=\"US\", format=\"*\") u'+12345678901' >>> PHONE_FORMAT(33234567890) Traceback (most recent call last): ... TypeError: Phone number must be a text value. If formatting a value from a Numeric column, convert that column to Text first.","title":"PHONE_FORMAT"},{"location":"functions/#proper","text":"Capitalizes each word in a specified string. It converts the first letter of each word to uppercase, and all other letters to lowercase. Same as text.title() . >>> PROPER('this is a TITLE') 'This Is A Title' >>> PROPER('2-way street') '2-Way Street' >>> PROPER('76BudGet') '76Budget'","title":"PROPER"},{"location":"functions/#regexextract","text":"Extracts the first part of text that matches regular_expression. >>> REGEXEXTRACT(\"Google Doc 101\", \"[0-9]+\") '101' >>> REGEXEXTRACT(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") '826.25' If there is a parenthesized expression, it is returned instead of the whole match. >>> REGEXEXTRACT(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") 'Content' >>> REGEXEXTRACT(\"Foo\", \"Bar\") Traceback (most recent call last): ... ValueError: REGEXEXTRACT text does not match","title":"REGEXEXTRACT"},{"location":"functions/#regexmatch","text":"Returns whether a piece of text matches a regular expression. >>> REGEXMATCH(\"Google Doc 101\", \"[0-9]+\") True >>> REGEXMATCH(\"Google Doc\", \"[0-9]+\") False >>> REGEXMATCH(\"The price today is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\") True >>> REGEXMATCH(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\") True >>> REGEXMATCH(\"Foo\", \"Bar\") False","title":"REGEXMATCH"},{"location":"functions/#regexreplace","text":"Replaces all parts of text matching the given regular expression with replacement text. >>> REGEXREPLACE(\"Google Doc 101\", \"[0-9]+\", \"777\") 'Google Doc 777' >>> REGEXREPLACE(\"Google Doc\", \"[0-9]+\", \"777\") 'Google Doc' >>> REGEXREPLACE(\"The price is $826.25\", \"[0-9]*\\.[0-9]+[0-9]+\", \"315.75\") 'The price is $315.75' >>> REGEXREPLACE(\"(Content) between brackets\", \"\\(([A-Za-z]+)\\)\", \"Word\") 'Word between brackets' >>> REGEXREPLACE(\"Foo\", \"Bar\", \"Baz\") 'Foo'","title":"REGEXREPLACE"},{"location":"functions/#replace","text":"Replaces part of a text string with a different text string. Position is counted from 1. >>> REPLACE(\"abcdefghijk\", 6, 5, \"*\") 'abcde*k' >>> REPLACE(\"2009\", 3, 2, \"10\") '2010' >>> REPLACE('123456', 1, 3, '@') '@456' >>> REPLACE('foo', 1, 0, 'bar') 'barfoo' >>> REPLACE('foo', 0, 1, 'bar') Traceback (most recent call last): ... ValueError: position invalid","title":"REPLACE"},{"location":"functions/#rept","text":"Returns specified text repeated a number of times. Same as text * number_times . The result of the REPT function cannot be longer than 32767 characters, or it raises a ValueError. >>> REPT(\"*-\", 3) '*-*-*-' >>> REPT('-', 10) '----------' >>> REPT('-', 0) '' >>> len(REPT('---', 10000)) 30000 >>> REPT('---', 11000) Traceback (most recent call last): ... ValueError: number_times invalid >>> REPT('-', -1) Traceback (most recent call last): ... ValueError: number_times invalid","title":"REPT"},{"location":"functions/#right","text":"Returns a substring of length num_chars from the end of a specified string. If num_chars is omitted, it is assumed to be 1. Same as string[-num_chars:] . >>> RIGHT(\"Sale Price\", 5) 'Price' >>> RIGHT('Stock Number') 'r' >>> RIGHT('Text', 100) 'Text' >>> RIGHT('Text', -1) Traceback (most recent call last): ... ValueError: num_chars invalid","title":"RIGHT"},{"location":"functions/#search","text":"Returns the position at which a string is first found within text, ignoring case. Find is case-sensitive. The returned position is 1 if within_text starts with find_text. Start_num specifies the character at which to start the search, defaulting to 1 (the first character of within_text). If find_text is not found, or start_num is invalid, raises ValueError. >>> SEARCH(\"e\", \"Statements\", 6) 7 >>> SEARCH(\"margin\", \"Profit Margin\") 8 >>> SEARCH(\" \", \"Profit Margin\") 7 >>> SEARCH('\"', 'The \"boss\" is here.') 5 >>> SEARCH(\"gle\", \"Google\") 4 >>> SEARCH(\"GLE\", \"Google\") 4","title":"SEARCH"},{"location":"functions/#substitute","text":"Replaces existing text with new text in a string. It is useful when you know the substring of text to replace. Use REPLACE when you know the position of text to replace. If instance_num is given, it specifies which occurrence of old_text to replace. If omitted, all occurrences are replaced. Same as text.replace(old_text, new_text) when instance_num is omitted. >>> SUBSTITUTE(\"Sales Data\", \"Sales\", \"Cost\") u'Cost Data' >>> SUBSTITUTE(\"Quarter 1, 2008\", \"1\", \"2\", 1) u'Quarter 2, 2008' >>> SUBSTITUTE(\"Quarter 1, 2011\", \"1\", \"2\", 3) u'Quarter 1, 2012'","title":"SUBSTITUTE"},{"location":"functions/#t","text":"Returns value if value is text, or the empty string when value is not text. >>> T('Text') u'Text' >>> T(826) u'' >>> T('826') u'826' >>> T(False) u'' >>> T('100 points') u'100 points' >>> T(AltText('Text')) u'Text' >>> T(float('nan')) u''","title":"T"},{"location":"functions/#tasteme","text":"For any given piece of text, decides if it is tasty or not. This is not serious. It appeared as an Easter egg, and is kept as such. It is in fact a puzzle to figure out the underlying simple rule. It has been surprisingly rarely cracked, even after reading the source code, which is freely available and may entertain Python fans. >>> TASTEME('Banana') True >>> TASTEME('Garlic') False","title":"TASTEME"},{"location":"functions/#text","text":"Converts a number into text according to a specified format. It is not yet implemented in Grist. You can use the similar Python functions str() to convert numbers into strings, and optionally format() to specify the number format. Note This function is not currently implemented in Grist.","title":"TEXT"},{"location":"functions/#trim","text":"Removes all spaces from text except for single spaces between words. Note that TRIM does not remove other whitespace such as tab or newline characters. >>> TRIM(\" First Quarter\\n Earnings \") 'First Quarter\\n Earnings' >>> TRIM(\"\") ''","title":"TRIM"},{"location":"functions/#upper","text":"Converts a specified string to uppercase. Same as text.upper() . >>> UPPER(\"e. e. cummings\") 'E. E. CUMMINGS' >>> UPPER(\"Apt. 2B\") 'APT. 2B'","title":"UPPER"},{"location":"functions/#value","text":"Converts a string in accepted date, time or number formats into a number or date. >>> VALUE(\"$1,000\") 1000 >>> assert VALUE(\"16:48:00\") - VALUE(\"12:00:00\") == datetime.timedelta(0, 17280) >>> VALUE(\"01/01/2012\") datetime.datetime(2012, 1, 1, 0, 0) >>> VALUE(\"\") 0 >>> VALUE(0) 0 >>> VALUE(\"826\") 826 >>> VALUE(\"-826.123123123\") -826.123123123 >>> VALUE(float('nan')) nan >>> VALUE(\"Invalid\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number >>> VALUE(\"13/13/13\") Traceback (most recent call last): ... ValueError: text cannot be parsed to a number","title":"VALUE"},{"location":"limits/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Limits # To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation. Number of documents # On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits . Number of collaborators # For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans. Number of tables per document # There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column. Rows per document # On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below. Data size # There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans. Uploads # Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail. API limits # Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit. Document availability # From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API. Legacy limits # Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Limits"},{"location":"limits/#limits","text":"To help you assess whether Grist will work for a use-case you have in mind, here is a list of limitations that apply to its operation.","title":"Limits"},{"location":"limits/#number-of-documents","text":"On all plans, the number of documents is not limited. To prevent accidental abuse of the system by automation tools, team sites may be limited to 1,000 documents. If you encounter such a limit for legitimate use, please contact support to increase it. Older free plans had a limit of ten documents. Learn more about legacy limits .","title":"Number of documents"},{"location":"limits/#number-of-collaborators","text":"For team sites on all plans, there is no limit on the number of team members that may be added to the site. For paid plans, the number of team members determines the price. See our pricing page for details. Team members added to your team site may inherit access to workspaces or documents within that organization. Learn more about team sharing . On both personal and team sites, each document may be shared with up to 2 free guests who do not affect the plan price even on paid plans.","title":"Number of collaborators"},{"location":"limits/#number-of-tables-per-document","text":"There is a limit of 500 tables allowed per document. This is a soft limit. If you find yourself with a large number of tables, consider merging ones that have the same structure. For example, if you have a table for each product type, consider using a single table with the product type as an extra column.","title":"Number of tables per document"},{"location":"limits/#rows-per-document","text":"On the Free plan, documents have a limit of 5,000 rows. The limit for Pro and Business plans is 100,000 and 150,000 rows, respectively. Documents are also subject to data size limits, as described below.","title":"Rows per document"},{"location":"limits/#data-size","text":"There is a hard limit to a document\u2019s total data size, determined as the row limit multiplied by 2KB. This means that documents on the Free plan have a data size limit of 10MB, with Pro and Business plan documents having limits of 200MB and 300MB, respectively. This value corresponds approximately to the size of the data in CSV format. You can see a document\u2019s current data size on the \u2018Raw Data\u2019 page . For memory and performance reasons, there\u2019s a recommended data size limit of 20MB. Documents beyond 20MB may slow down or run into memory limits depending on their complexity. As an example, a document with 100,000 rows and 24 numeric columns would reach this recommended limit. To help optimize formulas on large documents, you can use the built-in formula timer . Attachments are counted separately. Attachments plus data in a single document are limited to 1GB on all plans.","title":"Data size"},{"location":"limits/#uploads","text":"Uploads are limited to 50MB, both for attachments and to import data. Note that an import within this limit may result in a document that exceeds the document size limit, in which case the upload is still likely to fail.","title":"Uploads"},{"location":"limits/#api-limits","text":"Free plans are limited to 5,000 API calls per document per day. Pro and Business plans raise the limit to 40,000 and 60,000 calls per document per day, respectively. Free plans may be rate limited to 5 API requests per second per document. The team plan does not impose such a rate limit. Separately, there is a concurrency limit of 10 for all plans: if 10 authorized API requests are currently being processed for a particular document, any other API requests will be rejected (with HTTP status code 429) until at least one of the original requests completes. A client that waits for one request to complete before sending the next would not hit this limit (assuming it is the sole client accessing the document). The size of the body of any individual API request is limited to 1MB. In particular, this means that requests adding or updating multiple records may need to be split into batches that fit within this limit.","title":"API limits"},{"location":"limits/#document-availability","text":"From time to time, during upgrades and operational transitions, individual Grist documents may become inaccessible for a period of some seconds. Please bear this in mind when using Grist\u2019s API.","title":"Document availability"},{"location":"limits/#legacy-limits","text":"Older free personal plans have the following limits: 10 documents per site No workspaces 100,000 rows To determine if you\u2019re on a legacy personal site, click on your site name (@your-name) in the top left. Personal sites on the legacy plan will say \u201cPersonal Site (Legacy)\u201d in the dropdown menu. On the current Free plan, all personal and team sites share the same limits as described above and on our pricing page .","title":"Legacy limits"},{"location":"data-security/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Data Security # Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about. Grist SaaS # Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue . Self-Managed Grist # For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Data security"},{"location":"data-security/#data-security","text":"Grist is available as a hosted service (\u201cGrist SaaS\u201d) running on infrastructure managed by Grist Labs. It can also be installed on your own infrastructure (\u201cSelf-Managed Grist\u201d). In either case, we take measures to secure your data that you should know about.","title":"Data Security"},{"location":"data-security/#grist-saas","text":"Our general privacy policy and terms are at https://www.getgrist.com/privacy and https://www.getgrist.com/terms . In addition, here is a summary of specific measures that relate to Grist documents that we host on your behalf: Grist servers operate in the Amazon Web Services (AWS) cloud infrastructure, in the United States. AWS S3 is used for long-term storage, and stores documents in encrypted form. Data is stored in the United States. Grist employees never look at your data and cannot open your documents. The one exception is if you choose to share a document with customer support in order to get help with an issue. When being operated on, your data will by necessity exist in unencrypted form in some of Grist\u2019s internal systems. Only select key employees have full access to these systems, and policy prohibits them from looking inside documents. Secure HTTPS is used for all access to Grist via public internet (both website and API calls). Regular backups of Grist documents are made, and are stored in encrypted form. Grist retains more frequent snapshots of recent changes, and less frequent ones as you go back in time. If you delete a document, it will persist for 30 days in a Trash folder under your control. At any time a \u201cDelete forever\u201d option can be used to immediately purge a document in this folder. All automatic backups are purged along with the document. The hosted Grist product has not at this time gone through certification or auditing for SOC 2, ISO 27001, HIPAA, or GDPR compliance. If you need specific documentation, please contact customer support. You can also nudge us to prioritize certification over feature development at this issue .","title":"Grist SaaS"},{"location":"data-security/#self-managed-grist","text":"For Self-Managed Grist, you are in complete control of where servers operate and where data is stored. Here are some security considerations to bear in mind: Grist software is distributed via the gristlabs organization on github and docker hub . Please exercise diligence if accessing software elsewhere, since the software you install will have full access to your data. Grist documents support powerful Python formulas. Please pay attention to instructions for configuring sandboxing if your team may be working with untrusted documents. Grist by default is welcoming to anonymous users, allowing them to create and edit their own documents. You may wish to configure a stricter arrangement . Grist does not make external services mandatory, since that would introduce unnecessary obstacles in some scenarios. For example, an individual editing a Grist document offline on their own desktop shouldn\u2019t need to install a PostgreSQL database first. But it is important to evaluate what you need in your situation rather than simply sticking with the defaults. Please read about the data Grist stores and your options for where to store it. It is important to keep your Grist installation up to date .","title":"Self-Managed Grist"},{"location":"browser-support/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Browser Support # Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com . Mobile Support # You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Browser support"},{"location":"browser-support/#browser-support","text":"Grist is officially supported and regularly tested on modern Firefox and Google Chrome browsers on all desktop platforms. These are available here: Get Firefox Get Chrome Other modern browsers will work to the degree they are standards compliant. In particular, Grist is reported to work on modern Safari and Microsoft Edge. If you encounter errors or unexpected behavior on these browsers, we encourage you to report them to us by emailing us at support@getgrist.com .","title":"Browser Support"},{"location":"browser-support/#mobile-support","text":"You can use Grist quite comfortably in mobile browsers such as Chrome, Safari, and Firefox. On small screens, you can find the list of pages from the button in the bottom bar. For pages with multiple widgets, only the active widget is expanded. Other widgets are collapsed until you touch to expand them. To edit a cell, simply double-tap it. Mobile support is still a work in progress. To get back to the desktop version on a mobile device, open your account menu, and select \u201cToggle Mobile Mode\u201d option:","title":"Mobile Support"},{"location":"glossary/","text":"Warning We\u2019re just getting started translating this language, sorry! We show partially translated languages to track progress. This page isn\u2019t translated yet. But the good news is that you can join the translation community to help us \ud83d\udc4b . Glossary # Bar Chart # This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles. Column # A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity. Column Options # Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d. Column Type # Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc. Creator Panel # The creator panel is the right-side menu of configuration options for widgets and columns. Dashboard # A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets . Data Table # Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document. Document # A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document . Drag handle # This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo . Fiddle mode # Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ). Field # A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts. Import # To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ). Lookups # Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how. Page # Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page. Pie Chart # This is a classic chart type , where a circle is sliced up according to values in a column. Record # A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card. Row # A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities. Series # Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column. Sort # The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial . Trigger Formulas # A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps . User Menu # The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings. Widget # A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ). Widget Options # Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d. Wrap Text # Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Glossary"},{"location":"glossary/#glossary","text":"","title":"Glossary"},{"location":"glossary/#bar-chart","text":"This is a classic chart type , where the values in a column are shown as the heights of a series of rectangles.","title":"Bar Chart"},{"location":"glossary/#column","text":"A column is a vertical series of cells in a table. Columns in Grist have names. Each cell in a column is in a different row. When data from a column is present within a card, we call it a field. When a table of data is represented as a chart, we refer to each column as a series. From a data modeling perspective, columns typically have data about a single aspect of many real world entities, whereas rows have data about many aspects of a single entity.","title":"Column"},{"location":"glossary/#column-options","text":"Every column\u2019s appearance and behavior can be customized by clicking on the column header, clicking on the drop-down, and selecting \u201cColumn Options\u201d.","title":"Column Options"},{"location":"glossary/#column-type","text":"Columns have types, which control the appearance of cells in that column and the method used to edit them. You can change the column type at will. The Text Column Type is suited to strings of any length; the Date Column Type is specialized for storing and editing calendar dates; the Reference Column Type is for storing and editing links to other tables; the Numeric Column Type is for any kind of number; etc.","title":"Column Type"},{"location":"glossary/#creator-panel","text":"The creator panel is the right-side menu of configuration options for widgets and columns.","title":"Creator Panel"},{"location":"glossary/#dashboard","text":"A dashboard is just another name for a page, typically organized to give a summary or overview of a document\u2019s data. Grist is well suited to constructing dashboards, by creating pages with suitably linked widgets .","title":"Dashboard"},{"location":"glossary/#data-table","text":"Data is stored in tables. Tables have named columns , and a sequence of rows containing values for those columns. Every row has a numeric id (available as $id in formulas) that is unique within that table. The raw data page lists all data tables in your document.","title":"Data Table"},{"location":"glossary/#document","text":"A Grist document is a collection of related data. If you work with databases, think of it as a single database. If you work with spreadsheets, think of it as a single spreadsheet. Like databases and spreadsheets, the data in a Grist document is organized as a set of tables. How this data is viewed and edited is unusually flexible. Grist documents are organized visually into pages. Pages contain widgets that offer different ways to view and edit tables. To work with Grist, the first step is typically to create a document .","title":"Document"},{"location":"glossary/#drag-handle","text":"This is an icon to facilitate reorganizing views or lists visually. On a desktop computer, when hovering over a drag handle, the mouse cursor changes. The drag handle for a widget is just to the left of the widget\u2019s title. There is an example of using this drag handle in the investment research demo .","title":"Drag handle"},{"location":"glossary/#fiddle-mode","text":"Fiddle mode is a special mode that some Grist documents, such as the ones from the Examples & Templates page , will open in. A document opened in fiddle mode will have a \u201cfiddle\u201d label next to the document title in the top bar. In fiddle mode, any edit to a document will cause a new, unsaved copy (a.k.a \u201cfork\u201d) of that document to be created; the original document will remain unaffected. The copy can be saved via the \u201cSave Copy\u201d button or menu option. You can add /m/fork to the end of any document\u2019s URL to make that document open in fiddle mode (e.g. https://public.getgrist.com/3NsoHE2mWtEp/Lead-list/m/fork ).","title":"Fiddle mode"},{"location":"glossary/#field","text":"A field is a column shown in a Card Widget. The terms column, field, and series are not different in substance, but are different terms that make more sense for different widgets. In a Table Widget, we talk about columns. In a Chart Widget, we talk about series. And in a Card Widget, we talk about fields. A field has layout properties that are meaningful within a Card, but would not be meaningful in other contexts.","title":"Field"},{"location":"glossary/#import","text":"To import into Grist means to take data from other sources (on your computer or on the internet) and place that data in a Grist document. Examples of importing include: Take a CSV file on your computer, and create a Grist document with the same content (see: start a new Grist document ). Take an Excel file on your computer, and add the data from it to an existing Grist document (see: importing more data ). Take a JSON file on the internet, and add the data from it to an existing Grist document (see: importing more data ). Calling Grist\u2019s API from a program and adding data read from another source (see: Grist API ).","title":"Import"},{"location":"glossary/#lookups","text":"Lookup formulas allow you to \u201clook up\u201d data in other tables. lookupOne allows you to look up a single record in another table by matching some data across two tables, similar to Excel\u2019s VLOOKUP. lookupRecords allows you to look up multiple records in another table by matching some data across two tables. Lookups can be combined with dot notation to pull data from referenced records. Learn how.","title":"Lookups"},{"location":"glossary/#page","text":"Grist documents are organized visually into pages. Each page allows you to view or edit one or more tables. The nature of these viewers and editors (called \u201cpage widgets\u201d) is flexible, as is their layout. A single table can be viewed (or edited) from multiple widgets in one or many pages. And a single page can contain widgets for viewing (or editing) many tables. Pages are listed in the document ( in the panel on the left ). In this list, they may be rearranged and grouped, with several \u201csubpages\u201d nested within a single page.","title":"Page"},{"location":"glossary/#pie-chart","text":"This is a classic chart type , where a circle is sliced up according to values in a column.","title":"Pie Chart"},{"location":"glossary/#record","text":"A record is the data in one row of a table, comprising the data in the individual cells of that row. It has a unique identifier, usually hidden but available in formulas as id . In a Card Widget or a Card List Widget, a record is represented by a single card.","title":"Record"},{"location":"glossary/#row","text":"A horizontal series of cells in a table. Each cell in a row belongs to a different column. The data stored in a row is also called a record. Typically rows have data about different aspects a single entity, whereas columns have data about a single aspect of many entities.","title":"Row"},{"location":"glossary/#series","text":"Data from a single column shown in a Chart Widget is called a series. The same data in a Card Widget is called a field, and in a Table Widget is called a column.","title":"Series"},{"location":"glossary/#sort","text":"The order in which rows of a table are shown is called the sort order. An example of changing the sort order of a table is given in the CRM tutorial .","title":"Sort"},{"location":"glossary/#trigger-formulas","text":"A trigger formula is a formula that recalculates your data based on a set of conditions that you decide. They also allow you to clean data when a new value is entered ( watch webinar ), provide a sensible default value for a column or create Time and Authorship stamps .","title":"Trigger Formulas"},{"location":"glossary/#user-menu","text":"The user menu is the menu that opens by clicking your profile icon in the top-right of Grist. From there you can manage your profile, add additional Grist accounts that you own, and see a list of team sites to which you have access. Depending on where you are, the user menu will contain additional options. For example, from a personal site you\u2019ll see the option to upgrade your plan to a team site . From a team site, depending on your role and permissions, you may be able to manage billing or edit team members . From a document, you\u2019ll find an option to edit document settings.","title":"User Menu"},{"location":"glossary/#widget","text":"A page contains sections, such as table grids or charts, which we call \u201cpage widgets\u201d. They are used for viewing or editing data in tables. Types of page widgets include Card Widgets , Chart Widgets , as well as the classic spreadsheet-style table grid (called a Table Widget ).","title":"Widget"},{"location":"glossary/#widget-options","text":"Every page widget can have its appearance and behavior customized. How this is done varies between widget, but can always be done systematically by clicking on the three-dot menu on the top right of a widget and selecting \u201cWidget options\u201d.","title":"Widget Options"},{"location":"glossary/#wrap-text","text":"Normally, content that doesn\u2019t fit in the width of a cell is truncated, with \u201c\u2026\u201d indicating that part of the data is hidden. When \u201cWrap Text\u201d setting is enabled, long lines will wrap, and the cell will get taller to include all content. An example of wrapping is given in the CRM tutorial .","title":"Wrap Text"}]} \ No newline at end of file diff --git a/uk/sitemap.xml b/uk/sitemap.xml index eec96c8a7..c92969507 100644 --- a/uk/sitemap.xml +++ b/uk/sitemap.xml @@ -2,697 +2,697 @@ https://support.getgrist.com/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/FAQ/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/lightweight-crm/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/investment-research/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/afterschool-program/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/creating-doc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/copying-docs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/imports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/exports/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/automatic-backups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/document-history/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/workspaces/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/enter-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/page-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/raw-data/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/search-sort-filter/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-table/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-form/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-chart/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-calendar/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/widget-custom/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/linking-widgets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/custom-layouts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/record-cards/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/summary-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/on-demand-tables/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-types/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-refs/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/conditional-formatting/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/timestamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/authorship/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/col-transform/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formulas/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/references-lookups/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/dates/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-timer/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/python/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/formula-cheat-sheet/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/ai-assistant/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/teams/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/team-sharing/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/access-rules/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/rest-api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/integrators/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/embedding/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/webhooks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/code/modules/grist_plugin_api/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/self-managed/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/saml/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/oidc/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/forwarded-headers/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/cloud-storage/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/grist-connect/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/install/aws-marketplace/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-limited/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/telemetry-full/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2024-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2023-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2022-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-04/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-03/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-02/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2021-01/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-12/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-11/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-10/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-09/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-08/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-07/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-06/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/newsletters/2020-05/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-credit-card/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-06-book-club/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-07-email-compose/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-08-invoices/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-09-payroll/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-10-print-labels/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-11-treasure-hunt/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2020-12-map/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-01-tasks/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-03-leads/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-04-link-keys/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-05-reference-columns/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-06-timesheets/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2021-07-auto-stamps/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-01-acl-memo/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/examples/2023-07-proposals-contracts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/keyboard-shortcuts/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/functions/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/limits/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/data-security/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/browser-support/ - 2024-10-03 + 2024-10-09 daily https://support.getgrist.com/glossary/ - 2024-10-03 + 2024-10-09 daily \ No newline at end of file diff --git a/uk/sitemap.xml.gz b/uk/sitemap.xml.gz index 66759ed019973452c03df159a2b60f1fc02a8422..760920825d6cbbad0f2fa61ad7c223ffee0ecf70 100644 GIT binary patch delta 1071 zcmV+~1kn5R2=xemABzYGevbxc0{?SqbY*Q}a4vXlYyj1qO>g5i5J2zyD+1rgmgMXv zDI70)*?WQZycm;Xi7-D@l8WMg-;v@?)9$I4V#bFgrWEOc(##v$A`f3*gTH&BTwIFJ zn|9l5?kJjMUF@GXfBgOp9yec)pAJ*@h3Xye9Lw`&sCE2*Ww)E>d0SkifMeOF-0zAR z_)Xc}8h6e1X>CdA@=dXv|hn6>#-8;*Fruz2lZ$inu>xVki@)Jy! z+J7#-5TBBNVuehFFq=UrDV{3_g&9)vFy_=}l-j2z6IKpau*nETECXgHl-hx5yx#h+ zNR6AYv`M9-SIKlZ)f6~F44lwhrco*<-+t4Y83%4j`6?81PWe(sG$i(JjGVc_Vnv~V zBlZNdv)m}P4>Qh^d+@DtfWjN#>^xs@sll>dWvF<6@+2G4V79Ue&q-`q`=bxJ%&F2Z zv!`fLc)Xa24H(3ZwBltc7iDMgw=M*a*EGr144IVentP-omMH|x*S8cd6*5Jz6fj!Z zn1;vF^*V~?%XF5D7pgocOieVHyEF4lUgBvABOU76P7d{Kx#Ag-j zmosI5iab^YpOWOUKUT3>@pg@2iD;>H<9Ov@j#4{tie^dM(l4+X2rHCUJQ_~c1@a`fip^Zt0FF*-4~ivou~MJ0a@8gEqvLQQYvo4AOs9eC z7`X!!OOny}jB8$5o4=2K>RtRUH(m<)Gw`T?9(%H4Lz-Re{jOl}!V#0RMmQoDVl!q- z7Pu0#BsNX{;59x?7M3q7`4$zbTUTPMNG#5{`%1ufi30IKjNe$_v8qxd+Ug)C z-9y`{A=T5TjTTZZ-ruY7{$7pu_iDVqSL6M?8t?Dbcz>_P`+Jix0~wPq0}zu;10(@k zlW_wVe~tGWx!$K&45QrV?BDTj*BeYG>qM_q@DuVWr3)B8opb;{00lk?@TM^p(gFPl zF1{pZwA{!~Fyb{Qv5)%w6PGE*>|zCvC-OSFxNV_k!ZKxoVG=u4>|f9wEK4TJM_on; zP_bN!4%j_-LaPM!9+u@r;6hg=z@OoLk~M3?aZe#C0spd2XkKT#Dj|M=Whpxly{f`T z4l_3xc pnJK#}b*9ZJr@2su+eV?3y-%g!|1n)@{Z*(Q{{kRQrwv|P0048~2zLMg delta 1075 zcmV-31kC&O2=oYlABzYG@Rj~%0{?SqbY*Q}a4vXlYyj1qOLOBk41n+ZD>Avm)+4(~ zI*E6B*?VW&^A1eH632X~Bo)Q~zL4Tg)9$I44&Xx)Q;PHrCEx>XkxyS=gTHy9TwIFJ zx9zUEy`gB5b#ZvU{p0s<@NoO}@abvJzEHj4onv{v9c!I`e%bGrW!V*1Dd1FgDG&Q% zMt)QF*T#Lbd%Qh-y5ZM)fVaQ4*OO1?upXuGVahyBDc9XVb;z#Nok?N;v|o4m_#aqm zNcPY*-5sKqVt!h{fCw}l>Ix)f2R8O>u*BIyc@>4(E1Zh zmfC+Vz7U^(l46BSg)o~@C@G#Q2Zb3^@-XEzWR%*cCKFZ;SFp(lMJyv`CY0KNX}Vnd zuSkuXu(nC1qgTmvIM)<7LJShnuU1g|$c=RM2(O|Z+2~SCES^J|8xy-53 zFSDm;QFy$VnGG1kj%$K(mE)_CGuoN&_ z*_ejM()BWl=F4=Fix;h{E=Ba446#z5OMWr{pj1)q}Su|HO^S@C*}VU1|1b>np5V2)BdaE@k8+tM$v83`+tkx1$w1|482 z{0^g3#7Y>7U^bBbSv(p})&=q`w~EbN*8omVY7dGfbFtEpv2xWV^`ql(Bx~hH$3n-E z>lnEM6ibrP_>5a#*_yvkejZ%>E;n8Z`4jMes2&HhVndo;?ESuA@WK(3vqm@~7h(%$ zOBT2ivm`c6{@^t}%@)=#EBO``&bqaLNimYC!dY=C9QXN5b@FIPY&GdAP!;<{^*bMU z7hDyaL;vPH&0cC2jkFX`waQIO9|<>*=1~plgBsF%HKbqEkbYJ}dZ&i8S3}yVA#HUj zkoM4aYDo3;X`_Wyi}!mq-tX0TzgOe^UXAyAHQw*lc)wTU{a%gtJCh&-CX-VG9tzcX zzg6S?R*m-?lWqeSf3ElG6~iR=Is13K>-7ec$vV+175s#JOz8~9&qp1=_dtP90=#KT zg>*nafQv8587(*RBTRV7N$jJ3|HNgADZ5y~nXt^6V4THH75f)-2kVlF z@=@0j0#q#Lq64-EPiU3E-od)O2%PDn1o$(&PqJoh_&G!+ZQx(F3C-JVS0%*vur6f> zqE}V;h`c^J0BAe}3P}g={kqmCmp